app = FastAPI()@app.get("/async_task")
async def async_task():# 異步任務(wù)的代碼,并等待執(zhí)行完成
await some_async_function()
return {"message": "OK"}

但是有點(diǎn)我們是需要注意,上面的只是說我們實(shí)現(xiàn)了異步任務(wù),但是由于我們在接口中進(jìn)行await,所以盡管的任務(wù)是異步,但是還是需要等待,我們本節(jié)主要是學(xué)習(xí)相關(guān)異步化后臺(tái)的任務(wù),也就是說我們某些任務(wù)放置到后臺(tái)或其他地方去執(zhí)行,不影響當(dāng)前主線程的運(yùn)行。

BackgroundTasks異步化的后臺(tái)任務(wù)實(shí)現(xiàn)方式

在前面示例中,對(duì)于的任務(wù)執(zhí)行了await等待,就會(huì)進(jìn)行任務(wù)掛起并等待。假如some_async_function()的任務(wù)需要耗時(shí)比較久的話,且不在意它相關(guān)的處理結(jié)果,或者結(jié)果可以通過另一種方式進(jìn)行通知的話,我們可以把some_async_function()的任務(wù)進(jìn)行后臺(tái)話,可以理解為放到另一個(gè)線程去執(zhí)行,而不而不需要await等待,如之前的代碼,我們可以使用FasAPI框架所提供的BackgroundTasks的BackgroundTasks類來處理后臺(tái)任務(wù),如下示例代碼:

from fastapi import FastAPI, BackgroundTasks

app = FastAPI()def some_async_function():# 后臺(tái)任務(wù)的代碼
pass@app.get("/background_task")
async def background_task(background_tasks: BackgroundTasks):
background_tasks.add_task(some_async_function)
return {"message": "后臺(tái)任務(wù)添加成功"}

當(dāng)然上面這種方式也不合適長期運(yùn)行耗時(shí)的任務(wù),所以引出我們本小結(jié)主要需要了解的Celery庫,—芹菜. 使用Celery我們可以基于異步任務(wù)隊(duì)列的方式來處理異步任務(wù)。

Celery庫—芹菜.異步化的后臺(tái)任務(wù)實(shí)現(xiàn)方式

下面我一步一步的介紹如何基于Celery實(shí)現(xiàn)異步任務(wù)以及延遲任務(wù)。

首先我們從官網(wǎng)介紹總結(jié)一下關(guān)于Celery,它是一個(gè)基于Python的分布式任務(wù)隊(duì)列框架,用于實(shí)現(xiàn)異步任務(wù)的調(diào)度和執(zhí)行。它的主要作用是可以幫助我們將耗時(shí)的任務(wù)從主線程中分離出來,不因任務(wù)的耗時(shí)而空等,以此來提高我們系統(tǒng)的并發(fā)性和響應(yīng)速度。

Celery是用來處理異步任務(wù),所以我們可以使用它來處理以下類似的一些業(yè)務(wù)場景,例如發(fā)送電子郵件、生成報(bào)表、處理大量數(shù)據(jù)等。通過將這些任務(wù)放入任務(wù)隊(duì)列中,可以讓主線程繼續(xù)處理其他請求,而不需要等待任務(wù)完成。當(dāng)然Celery還提供定時(shí)任務(wù)的調(diào)度功能,可以讓我們按照設(shè)定的時(shí)間間隔或者時(shí)間點(diǎn)執(zhí)行任務(wù)。

Celery簡單使用步驟

通常我們使用Celery需要進(jìn)行以下相關(guān)以下大致的幾個(gè)步驟:

  1. 實(shí)例化Celery對(duì)象來創(chuàng)建一個(gè)Celery應(yīng)用。
  2. 基于已實(shí)例化的任務(wù)的實(shí)例對(duì)象來定于該實(shí)例所包含的任務(wù):通常我們是使用裝飾器將函數(shù)注冊為Celery任務(wù),并設(shè)置任務(wù)的參數(shù)和返回值。(加入我們的是在相關(guān)的Fastapi框架進(jìn)行使用,通常我們需要做就是實(shí)例化對(duì)象并聲明任務(wù),并進(jìn)行任務(wù)的發(fā)布,通常是通過調(diào)用Celery應(yīng)用的apply_async()方法來提交任務(wù)到任務(wù)隊(duì)列中。)

3.當(dāng)我們的任務(wù)發(fā)布之后,任務(wù)會(huì)進(jìn)入一個(gè)消息隊(duì)列里面進(jìn)行等待,等待消費(fèi)者去消費(fèi),所以接下里我們需要啟動(dòng)Celery Worker:啟動(dòng)Celery Worker的消費(fèi)者對(duì)象來處理任務(wù)隊(duì)列中的任務(wù)。

例如:

1 定義任務(wù)和發(fā)布任務(wù)

主要我們這里使用消息代理的中間件是redis,所以是先啟動(dòng)我們的redis,

接著定義相關(guān)任務(wù),如下示例代碼:

使用uvicorn.run函數(shù)運(yùn)行了一個(gè)應(yīng)用程序。它指定了應(yīng)用程序的主機(jī)和端口,并且設(shè)置了reload參數(shù)為True。

uvicorn.run(f"{inspect.getmodulename(Path(__file__).name)}:app", host='127.0.0.1', port=31110, reload=True,workers=1)
app = FastAPI()
celery_app = Celery("tasks", broker="redis://localhost:6379/0")

# 定義任務(wù)
@celery_app.task
def some_celery_task():

# Celery任務(wù)的代碼
pass

@app.get("/celery_task")
async def celery_task():

# 開始發(fā)布任務(wù)
some_celery_task.delay()
return {"message": "Celery 任務(wù)發(fā)布成功"}

if __name__ == "__main__":

# 使用os.path.basename函數(shù)獲取了當(dāng)前文件的名稱,并將.py文件擴(kuò)展名替換為空字符串

# import os

# app_modeel_name = os.path.basename(__file__).replace(".py", "")
from pathlib import Path

# 使用Path函數(shù)獲取了當(dāng)前文件的名稱,并將.py文件擴(kuò)展名替換為空字符串

# app_modeel_name = Path(__file__).name.replace(".py", "")
import uvicorn
import inspect

# 根據(jù)文件路徑返回模塊名

# print("app_modeel_name:",inspect.getmodulename(Path(__file__).name))

# 使用uvicorn.run函數(shù)運(yùn)行了一個(gè)應(yīng)用程序。它指定了應(yīng)用程序的主機(jī)和端口,并且設(shè)置了reload參數(shù)為True。
uvicorn.run(f"{inspect.getmodulename(Path(__file__).name)}:app", host='127.0.0.1', port=31110, reload=True,workers=1)

啟動(dòng)應(yīng)用并發(fā)布任務(wù):


FastAPI應(yīng)用程序,當(dāng)調(diào)用 some_celery_task.delay() 來發(fā)布任務(wù)時(shí),Celery將把任務(wù)放入Redis隊(duì)列中。

2 啟動(dòng)Celery worker進(jìn)程,進(jìn)行任務(wù)消費(fèi)

在命令行中,切換到您的項(xiàng)目目錄。啟動(dòng)Celery worker進(jìn)程。在命令行中運(yùn)行以下命令:

celery -A main.celery_app worker --loglevel=info

啟動(dòng)后得到如下圖所示的結(jié)果:

Celery worker進(jìn)程將從Redis隊(duì)列中獲取任務(wù),并執(zhí)行任務(wù)所定義的代碼。當(dāng)任務(wù)被執(zhí)行時(shí),您將在Celery worker進(jìn)程的日志中看到相關(guān)的日志消息。但是我們運(yùn)行時(shí)候遇到了問題如下:

Did you remember to import the module containing this task?
Or maybe you're using relative imports?

Please see
https://docs.celeryq.dev/en/latest/internals/protocol.html
for more information.The full contents of the message body was:
b'[[], {}, {"callbacks": null, "errbacks": null, "chain": null, "chord": null}]' (77b)The full contents of the message headers:
{'lang': 'py', 'task': 'main_celely.some_celery_task', 'id': 'e134d62c-0d8c-46b3-86a8-981411f41eac', 'shadow': None, 'eta': None, 'expires': None, 'group': None, 'group_index': None, 'retries': 0, 'timelimit': [None, None], 'root_id': 'e134d62c-0d8c-46b3-86a8-981411f41eac', 'parent_id': None, 'argsrepr': '()', 'kwargsrepr': '{}', 'origin': 'gen15648@xiaozhong', 'ignore_result': False, 'stamped_headers': None, 'stamps': {}}The delivery info for this task is:
{'exchange': '', 'routing_key': 'celery'}
Traceback (most recent call last):
File "D:code_loaclmm_ring_v2venvLibsite-packagesceleryworkerconsumerconsumer.py", line 642, in on_task_received
strategy = strategies[type_]
~~~~~~~~~~^^^^^^^

出現(xiàn)上述原因問題在于我們的:

@celery_app.task
def some_celery_task():

# Celery任務(wù)的代碼
pass

沒有進(jìn)行相關(guān)實(shí)例綁定,我們可以修改為:

@celery_app.task(bind=True)
def some_celery_task(self):

# Celery任務(wù)的代碼
import time
time.sleep(10)
pass

然后每次發(fā)布任務(wù)的時(shí)候,重新觀察消費(fèi)者的任務(wù)輸出信息如下:

如上的輸出,表示我們的任務(wù)已經(jīng)被正常進(jìn)行消費(fèi)了!

Celery 詳解之相關(guān)參數(shù)項(xiàng)

在上面實(shí)例我們已簡單完成相關(guān)異步任務(wù)的處理,通常我們一般需要使用提各種參數(shù)來配置和控制其行為。下面介紹一下Celery一些常用的配置項(xiàng):

PS:配置選項(xiàng)的名稱和具體含義可能會(huì)因Celery的版本而有所不同。

  1. broker:
  2. backend:
  3. include:
  4. task_track_started:
  5. task_time_limit:
  6. task_soft_time_limit:
  7. task_acks_late:
  8. task_ignore_result:

除了之前提到的常用配置項(xiàng)外,以下是一些其他常見的Celery配置項(xiàng)的說明:

  1. task_serializer:
  2. result_serializer:
  3. task_default_queue:
  4. task_default_exchange:
  5. task_default_routing_key:
  6. worker_concurrency:
  7. worker_prefetch_multiplier:
  8. beat_schedule:

······其他參數(shù)說明,有需要我得話,我在去官方文檔查閱接口。接下里我,我們看看和它對(duì)于就是所謂得配置文件。配置文件其實(shí)和上面所謂得參數(shù)是沒有區(qū)別得,也就是說,我們可以使用其他的方式來定義我們的參數(shù)傳入,如這些配置項(xiàng)我們還可以可以在Celery的配置文件中進(jìn)行設(shè)置,通常命名為celeryconfig.py或celery.py。如下的示例代碼所示:

下面是一個(gè)使用配置文件的示例:

  1. 首先我們創(chuàng)建一個(gè)名為celeryconfig.py的配置文件,里面包含的內(nèi)容如下:

每30秒執(zhí)行一次

}

}

# celeryconfig.py
from datetime import timedelta
from celery.schedules import crontab

broker_url = 'amqp://guest:guest@localhost//'

# 消息代理的URL
result_backend = 'redis://localhost:6379/0'

# 結(jié)果存儲(chǔ)后端的URL
task_serializer = 'json'

# 任務(wù)的序列化器
result_serializer = 'json'

# 任務(wù)結(jié)果的序列化器
timezone = 'Asia/Shanghai'

# 使用的時(shí)區(qū)
task_acks_late = True

# 啟用延遲確認(rèn)
task_ignore_result = False

# 不忽略任務(wù)結(jié)果
task_soft_time_limit = 60

# 任務(wù)的軟時(shí)間限制為60秒
task_time_limit = 120

# 任務(wù)的硬時(shí)間限制為120秒
worker_prefetch_multiplier = 4

# 任務(wù)執(zhí)行者的預(yù)取乘數(shù)
worker_concurrency = 8

# 每個(gè)任務(wù)執(zhí)行者的并發(fā)處理數(shù)量
worker_max_tasks_per_child = 100

# 每個(gè)任務(wù)執(zhí)行者最大處理任務(wù)數(shù)
task_default_priority = 0

# 任務(wù)的默認(rèn)優(yōu)先級(jí)
task_routes = {
'myapp.tasks.email_task': {'queue': 'email_queue'},

# 指定任務(wù)的隊(duì)列
'myapp.tasks.image_task': {'queue': 'image_queue'}
}
worker_hijack_root_logger = False

# 不重寫根日志記錄器
worker_disable_rate_limits = False

# 不禁用任務(wù)速率限制
beat_schedule = {
'task1': {
'task': 'myapp.tasks.task1',
'schedule': crontab(minute='*/15'),

# 每15分鐘執(zhí)行一次
},
'task2': {
'task': 'myapp.tasks.task2',
'schedule': timedelta(seconds=30),

# 每30秒執(zhí)行一次
}
}

在你的Celery應(yīng)用程序中加載配置文件。

# celery_app.py

from celery import Celeryapp = Celery('myapp')
app.config_from_object('celeryconfig')

在上述示例中,我們創(chuàng)建了一個(gè)名為celeryconfig.py的配置文件,并在其中設(shè)置了各種Celery的配置選項(xiàng)。然后,我們在Celery應(yīng)用程序的入口文件celery_app.py中加載了該配置文件。

你可以根據(jù)自己的需求和應(yīng)用程序的特定要求,在配置文件中進(jìn)行相應(yīng)的配置。注意,Celery的配置文件應(yīng)該位于與你的Celery應(yīng)用程序代碼相同的目錄中,或者可以通過正確的路徑進(jìn)行引用。

確保在啟動(dòng)Celery應(yīng)用程序時(shí),使用正確的配置文件加載配置。例如,通過以下命令啟動(dòng)Celery Worker:

celery -A celery_app worker --loglevel=info

PS: 通常win系統(tǒng)下可能你需要使用如下的方式啟動(dòng):

#  D:code_loaclmm_ring_v2> celery -A src.tasks.app worker -n migutasks    --loglevel=info -P eventlet

# celery -A src.tasks.app worker --loglevel=info -P eventlet

# celery -A src.tasks.app flower --address=127.0.0.1 --port=5559

# celery -A tasks worker --loglevel=info -P eventlet

# 啟動(dòng)celery監(jiān)控和管理Flower:

# celery -A src.tasks.app flower --address=127.0.0.1 --port=5555

獲取使用代碼的方式啟動(dòng):

from src.tasks.app import celery_app

if __name__ == '__main__':# python -m celery -A src.tasks.app worker -Q mm_ring_v2 --loglevel=info -P eventlet
celery_app.worker_main(argv=['-A', 'src.tasks.app','worker','--loglevel=info', '-Q', 'mm_ring_v2','-P', 'eventlet'])

這樣,Celery將會(huì)根據(jù)配置文件中的設(shè)置來運(yùn)行任務(wù)調(diào)度和執(zhí)行過程。

Celery延時(shí)任務(wù)的執(zhí)行

延遲任務(wù)場景我們也通常會(huì)遇到,比如超時(shí)未支付則自當(dāng)取消訂單等。下面舉例說明一下具體的實(shí)現(xiàn)過程:

基本任務(wù)項(xiàng)目結(jié)果:

1:定義Celery實(shí)例對(duì)象

celery -A src.tasks.app flower –address=127.0.0.1 –port=5555

from src.tasks.service import TaskFactory

# 任務(wù)工廠,可以創(chuàng)建多個(gè),目前只是創(chuàng)建了一個(gè)celery_app對(duì)象
factory = TaskFactory()
# 創(chuàng)建第一個(gè) Celery 應(yīng)用
celery_app = factory.create_celery_app(namespces='mm_sync_tasks', config_name='src.tasks.config.ProductionConfig')
celery_app.conf["worker_redirect_stdouts"] = False

# 禁止重定向工作進(jìn)程的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤流

def get_pending_tasks():
with celery_app.connection() as connection:
tasks = connection.default_channel.queue_declare(queue='celery', passive=True).message_count
return tasks

# 移除任務(wù)隊(duì)列中的所有等待執(zhí)行的任務(wù)
def remove_pending_tasks():
with celery_app.connection() as connection:
connection.default_channel.queue_purge(queue='celery')

# 重新調(diào)度任務(wù)
def reschedule_tasks():
pending_tasks = get_pending_tasks()
remove_pending_tasks()

# task_prerun:任務(wù)開始運(yùn)行前觸發(fā)的信號(hào)。
# task_postrun:任務(wù)運(yùn)行完成后觸發(fā)的信號(hào)。
# task_success:任務(wù)成功完成時(shí)觸發(fā)的信號(hào)。
# task_failure:任務(wù)失敗時(shí)觸發(fā)的信號(hào)。
# task_retry:任務(wù)重試時(shí)觸發(fā)的信號(hào)。
# task_revoked:任務(wù)被撤銷時(shí)觸發(fā)的信號(hào)。
# task_rejected:任務(wù)被拒絕時(shí)觸發(fā)的信號(hào)。
# worker_ready:Worker準(zhǔn)備就緒時(shí)觸發(fā)的信號(hào)。

@task_retry.connect
def handle_task_failure(sender: Task, task_id: str, exception: Exception, traceback, einfo, **kwargs):
if isinstance(exception, MaxRetriesExceededError):

# 處理 MaxRetriesExceededError 異常
print("全局異常錯(cuò)誤獲取!!", sender, task_id)
pass

@task_retry.connect
def handle_task_failure(sender: Task, task_id: str, exception: Exception, traceback, einfo, **kwargs):
if isinstance(exception, MaxRetriesExceededError):

# 處理 MaxRetriesExceededError 異常
print("全局異常錯(cuò)誤獲取!!", sender, task_id)
pass

@task_failure.connect
def handle_task_failure(sender: Task, task_id: str, exception: Exception, traceback, einfo, **kwargs):

# # 處理任務(wù)失敗的邏輯
if isinstance(exception, MaxRetriesExceededError):

# 處理 MaxRetriesExceededError 異常
print("全局異常錯(cuò)誤獲取!!", sender, task_id)
pass

@task_success.connect
def handle_task_success(sender: Task, result, **kwargs):

# 處理任務(wù)成功完成的邏輯
print("handle_task_success!", sender, result)
pass

if __name__ == '__main__':
pass

# D:code_loaclmm_ring_v2> celery -A src.tasks.app worker -n migutasks --loglevel=info -P eventlet

# celery -A src.tasks.app worker --loglevel=info -P eventlet

# celery -A src.tasks.app flower --address=127.0.0.1 --port=5559

# celery -A tasks worker --loglevel=info -P eventlet

# 啟動(dòng)celery監(jiān)控和管理Flower:

# celery -A src.tasks.app flower --address=127.0.0.1 --port=5555

定義實(shí)例配置信息:

class ProductionConfig(Config):
DEBUG = False

@Modify Time      @Author    @Version    @Desciption
------------ ------- -------- -----------
2023/6/16 11:25 小鐘同學(xué) 1.0 None
'''
from kombu import Queue, Exchange

class Config:

# broker = 'redis://127.0.0.1:6379/1' # 任務(wù)儲(chǔ)存

# backend = 'redis://127.0.0.1:6379/2' # 結(jié)果存儲(chǔ),執(zhí)行完之后結(jié)果放在這
BROKER_URL = 'redis://127.0.0.1:6379/1'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/2'

CELERY_TIMEZONE = 'Asia/Shanghai'
CELERY_ENABLE_UTC = True

# 全局的任務(wù)過期時(shí)間

# app.conf.update(result_expires=60)

# 定義隊(duì)列名稱
CELERY_QUEUES = (
Queue('mm_ring_v2'),
)

# CELERY_QUEUES = (

# Queue('default', exchange=Exchange('default'), routing_key='default'),

# Queue('app_task1', exchange=Exchange('app_task1'), routing_key='app_task1'),

# Queue('app_task2', exchange=Exchange('app_task2'), routing_key='app_task2'),

# )

# 指定任務(wù)走什么對(duì)了和routing_key

# CELERY_ROUTES = {

# 'src.tasks.task.migu_order_sync_status': {'queue': 'app_task1', 'routing_key': 'app_task1'},

# 'celery_app.task.task2': {'queue': 'app_task2', 'routing_key': 'app_task2'}

# }

# 指定需要加載的任務(wù)

# 指定要導(dǎo)入的任務(wù)模塊或任務(wù)文件列表

# celery_app.conf.update(

# include=[

# 'src.tasks.task.migu_order_sync_status',

# ]

# )

# 配置需要執(zhí)行的任務(wù)所在的目錄
CELERY_INCLUDE = [
'src.tasks.task.migu_order_sync_status',
]

class DevelopmentConfig(Config):
DEBUG = True

class ProductionConfig(Config):
DEBUG = False

2:定義任務(wù):

import datetime
import signal

from celery import current_app
from celery.result import AsyncResultfrom pydantic import BaseModeldef exponential_backoff(retries):
return 2 ** retries# 定義延遲執(zhí)行的任務(wù)
# expires=3600 將任務(wù)結(jié)果的存儲(chǔ)有效期設(shè)置為 1 小時(shí)。
@celery_app.task(bind=True, max_retries=5, default_retry_delay=1, retry_backoff=exponential_backoff(2), expires=3660)
def action_migu_order_sync_status(self, orderid, mobile):# self.request.retries 表示當(dāng)前重試的次數(shù)
······

需要說明一點(diǎn)是,@celery_app.task:這是一個(gè)裝飾器,用于將一個(gè)普通的Python函數(shù)注冊為Celery任務(wù)。celery_app 是你的 Celery 應(yīng)用實(shí)例,task 是 Celery 提供的裝飾器函數(shù)。且這個(gè)的任務(wù)相關(guān)參數(shù)項(xiàng)說明如下

3:在API接口發(fā)布任務(wù)

@router.get("/put/code", summary="訂單提交")
def callback(*, forms: PutCodeForm = Depends()):
.....

# 發(fā)布任務(wù)
result = action_migu_order_sync_status.apply_async(args=(Orders.orderid, Orders.mobile), countdown=60, retry=5,eta=eta, expires=expires,queue='mm_ring_v2') if result == -1:
return Fail(message='提交失敗') return Success(message='提交成功')

各個(gè)個(gè)參數(shù)項(xiàng)說明如下:

  1. args=(Orders.orderid, Orders.mobile):這個(gè)參數(shù)用于指定要傳遞給 Celery 任務(wù)的位置參數(shù),即任務(wù)函數(shù)在執(zhí)行時(shí)所需的參數(shù)值。在這個(gè)例子中,任務(wù)函數(shù)需要兩個(gè)參數(shù),分別對(duì)應(yīng) Orders.orderidOrders.mobile
  2. countdown=60:指定任務(wù)在被放入隊(duì)列之后需要延遲多少秒才開始執(zhí)行。在這個(gè)例子中,任務(wù)會(huì)在被加入隊(duì)列后延遲 60 秒后開始執(zhí)行。
  3. retry=5:指定任務(wù)在發(fā)生錯(cuò)誤時(shí)最多重試的次數(shù)。與之前提到的 max_retries 類似,但這里是針對(duì)單次任務(wù)調(diào)用的重試次數(shù)。
  4. eta=eta:這個(gè)參數(shù)用于指定任務(wù)的預(yù)計(jì)執(zhí)行時(shí)間。通常用于將任務(wù)調(diào)度到未來的某個(gè)時(shí)間點(diǎn)執(zhí)行,而不是立即執(zhí)行。
  5. expires=expires:同樣的意義,指定任務(wù)的過期時(shí)間,單位為秒。如果任務(wù)在指定的時(shí)間內(nèi)沒有被執(zhí)行,將會(huì)被標(biāo)記為過期并丟棄。
  6. queue='mm_ring_v2':指定任務(wù)被發(fā)送到的隊(duì)列名稱。Celery 支持將任務(wù)發(fā)送到不同的隊(duì)列中,以便進(jìn)行任務(wù)的分類和分配。

綜合起來,這些參數(shù)的設(shè)置使得對(duì)該 Celery 任務(wù)的調(diào)用具有了一定的靈活性。你可以控制任務(wù)的延遲執(zhí)行、重試次數(shù)、預(yù)計(jì)執(zhí)行時(shí)間以及過期時(shí)間,并且可以指定任務(wù)發(fā)送到哪個(gè)隊(duì)列中。

4:啟動(dòng)消費(fèi)者

if __name__ == '__main__':
pass

# D:code_loaclmm_ring_v2> celery -A src.tasks.app worker -n migutasks --loglevel=info -P eventlet# celery -A src.tasks.app worker --loglevel=info -P eventlet# celery -A src.tasks.app flower --address=127.0.0.1 --port=5559# celery -A tasks worker --loglevel=info -P eventlet

5:啟動(dòng)celery監(jiān)控和管理Flower


本文章轉(zhuǎn)載微信公眾號(hào)@程序員小鐘同學(xué)

熱門推薦
一個(gè)賬號(hào)試用1000+ API
助力AI無縫鏈接物理世界 · 無需多次注冊
3000+提示詞助力AI大模型
和專業(yè)工程師共享工作效率翻倍的秘密
熱門推薦
一個(gè)賬號(hào)試用1000+ API
助力AI無縫鏈接物理世界 · 無需多次注冊
返回頂部
上一篇
如何在 Apifox 中發(fā)布多語言的 API 文檔?
下一篇
FastAPI-Cache2:一個(gè)讓接口飛起來的緩存神器
国内精品久久久久影院日本,日本中文字幕视频,99久久精品99999久久,又粗又大又黄又硬又爽毛片
免费人成在线不卡| 国产精品一区二区久激情瑜伽| 欧美激情一区在线| 黄色资源网久久资源365| 日韩一级免费一区| 精品一区二区三区视频| 精品国产乱码91久久久久久网站| 老司机精品视频一区二区三区| 欧美日韩精品免费| 奇米四色…亚洲| 久久久不卡网国产精品二区| 成人免费高清在线| 午夜精品福利一区二区三区av| 日韩欧美中文一区| 国产精品一区二区三区99| 国产精品久久久久影院老司| 欧美午夜精品久久久| 蜜桃一区二区三区在线观看| 久久久国产午夜精品| 在线免费观看日本欧美| 美国精品在线观看| 亚洲免费视频中文字幕| 日韩一区二区三| 91浏览器在线视频| 精品一二三四区| 亚洲激情欧美激情| 国产三级欧美三级日产三级99 | 一区二区三区视频在线看| 欧美日韩极品在线观看一区| 国产高清亚洲一区| 香蕉久久一区二区不卡无毒影院 | 欧美日韩高清一区二区不卡| 国产一区二区毛片| 天涯成人国产亚洲精品一区av| 久久精品亚洲一区二区三区浴池| 欧美日韩aaa| 色综合久久88色综合天天免费| 国产一区二区在线观看视频| 日韩精品电影在线| 亚洲午夜电影网| 中文字幕一区二区视频| 国产精品午夜在线| 国产欧美视频一区二区| 欧美成人aa大片| 欧美一三区三区四区免费在线看| 色婷婷精品大在线视频| av一区二区三区在线| 丁香婷婷综合色啪| 国产成人自拍高清视频在线免费播放| 日本中文字幕不卡| 青青草视频一区| 美女一区二区在线观看| 日本欧美加勒比视频| 日本人妖一区二区| 久久99九九99精品| 国产原创一区二区| 成人一区二区三区在线观看| 成人小视频在线| 在线观看日韩毛片| 欧美二区在线观看| 欧美电影免费提供在线观看| 欧美不卡视频一区| 久久久久国色av免费看影院| 国产精品嫩草影院av蜜臀| 亚洲精选免费视频| 日韩av一区二区三区四区| 国内国产精品久久| 91免费版在线看| 日韩一区二区在线观看视频播放| 日韩免费电影网站| 国产欧美精品区一区二区三区| 国产精品久久久久一区二区三区共 | 欧美久久高跟鞋激| 精品美女一区二区| 国产精品成人在线观看| 亚洲一区二区影院| 国产最新精品免费| 91免费看`日韩一区二区| 91精品国产综合久久蜜臀| 国产午夜精品久久久久久免费视| 久久久三级国产网站| 久久97超碰国产精品超碰| 韩国欧美国产1区| 99久久国产综合精品色伊| 色综合久久久久综合99| 欧美老肥妇做.爰bbww| 国产欧美日产一区| 亚洲va韩国va欧美va精品| 黄色精品一二区| 91搞黄在线观看| 国产日产欧美一区二区视频| 婷婷丁香激情综合| 色综合天天综合网国产成人综合天| 91精品婷婷国产综合久久性色| 国产三级一区二区三区| 日韩av在线发布| 欧美日韩国产高清一区二区| 亚洲国产高清不卡| 人禽交欧美网站| 欧美肥胖老妇做爰| 亚洲地区一二三色| 91丨porny丨国产| 国产精品久久久久三级| 国产成人精品三级| 精品国产乱码久久久久久1区2区| 亚洲1区2区3区视频| 欧美亚洲日本国产| 一区二区成人在线观看| 91原创在线视频| 亚洲激情校园春色| 欧洲一区二区av| 亚洲一区二区四区蜜桃| 色美美综合视频| 一区二区在线观看免费| 日本精品一级二级| 亚洲综合精品久久| 在线电影欧美成精品| 日本欧美在线观看| 精品欧美一区二区在线观看| 久久激五月天综合精品| 日韩欧美中文字幕公布| 国产美女一区二区| 国产精品蜜臀在线观看| 日本韩国精品在线| 午夜精品久久久久久久久久| 欧美一区二区三区成人| 国产在线播精品第三| 国产精品系列在线| 91国偷自产一区二区三区观看| 亚洲一卡二卡三卡四卡| 欧美一区二区三区视频| 国产91丝袜在线播放九色| 最新不卡av在线| 8v天堂国产在线一区二区| 国产中文字幕精品| 亚洲欧美成aⅴ人在线观看| 欧美久久久影院| 国产成人8x视频一区二区| 亚洲日本乱码在线观看| 日韩一级大片在线观看| www.欧美色图| 日韩影院在线观看| 中文字幕一区二区日韩精品绯色| 欧美丝袜第三区| 成人污视频在线观看| 日韩精品电影在线| 亚洲三级免费电影| 久久先锋影音av鲁色资源网| 在线观看av一区| 成人高清免费观看| 蜜臀va亚洲va欧美va天堂 | 国产精品自在在线| 亚洲国产精品久久久久秋霞影院| 精品日韩在线一区| 91精品一区二区三区久久久久久| 色综合天天做天天爱| 高清shemale亚洲人妖| 蜜臀av国产精品久久久久| 亚洲精品国产一区二区精华液| 精品久久久久久久久久久院品网| 色综合天天在线| 成人综合在线观看| 久久99九九99精品| 日本中文在线一区| 五月天久久比比资源色| 亚洲一区在线观看视频| 中文字幕人成不卡一区| 国产欧美中文在线| 国产午夜精品一区二区三区视频 | 午夜精品一区二区三区免费视频| 国产精品护士白丝一区av| 久久夜色精品一区| 91精品国产综合久久久久久 | 色综合天天综合在线视频| 成人高清免费在线播放| 国产成人在线免费观看| 国产福利一区二区三区视频| 国产成人免费视频一区| 国产一区二区美女诱惑| 国产精品自拍三区| 成人一区二区视频| 色88888久久久久久影院按摩 | 国产精品一品二品| 成人av网站在线观看| 91色porny蝌蚪| 欧美影视一区二区三区| 欧美高清你懂得| 久久精品一区二区三区不卡牛牛| 国产精品美女久久久久久久| 亚洲人成网站色在线观看| 亚洲综合无码一区二区| 图片区小说区区亚洲影院| 老司机精品视频在线| 成人性生交大合| 欧美日本高清视频在线观看| 久久亚洲欧美国产精品乐播| 亚洲人成7777| 国内精品写真在线观看| 91福利视频久久久久| 久久久久久日产精品|