【第 1 章:为什么需要异步任务与消息队列】

1️⃣ 本章要解决什么问题(Why)

你在做一个 Web 服务(比如下单、注册、生成报告、发邮件、图片处理)。典型的“天真写法”是:

用户请求来了 → 服务器把所有事情都做完 → 最后再返回响应

这在低并发时没问题,但一上量就会出现三个工程灾难:

  1. 响应时间爆炸
  • 发邮件 2s、调用第三方 3s、生成 PDF 10s
  • 用户等 15 秒,前端超时,体验崩了
  1. 吞吐下降 + 资源被拖死
  • Web 线程/进程被长任务占着
  • 新请求进不来,排队堆积,最后雪崩
  1. 故障扩散(最关键)
  • 第三方接口抖一下,你的主链路就跟着挂
  • “下单成功”这种核心路径被“发短信失败”拖垮,非常不划算

所以我们需要把系统拆成两类工作:

  • 必须立刻完成的(同步):返回给用户的核心逻辑
  • 可以稍后完成的(异步):耗时、可重试、可并行的任务

这就是“异步任务”和“消息队列”出现的根本原因:

把主链路变短,把非关键工作移出请求线程,用队列实现解耦、削峰、可靠交付。


2️⃣ 核心概念与角色(What)

先把名词讲清楚(这些会贯穿 RabbitMQ + Celery 全套体系):

✅ 异步任务(Async Task)

  • “现在先别做,稍后做”
  • 关键点:提交任务执行任务 分离

✅ 消息队列(Message Queue, MQ)

  • 一种“中间缓冲层”
  • 生产者把消息放进去,消费者从里面取出来处理
  • 关键价值:解耦、削峰、异步、可靠传递

✅ 角色关系说明(必须记住)

  • Producer(生产者):产生任务的人(通常是你的 Web 服务)
  • Broker(消息代理):队列系统(RabbitMQ 就是它)
  • Consumer/Worker(消费者/工人):执行任务的人(Celery worker)
  • Message/Task(消息/任务):传递的工作描述(“做什么 + 参数”)

用一个非常直白的类比:

1
2
3
4
Web 服务 = 前台收银员(Producer)
RabbitMQ = 取号机 + 等候区(Broker)
Celery Worker = 后厨厨师(Consumer)
消息/任务 = 订单小票(Message

3️⃣ 工作原理(How)

3.1 从“同步直做”到“异步投递”

同步直做(问题模型)

1
2
Client -> Web Server -> (发邮件/调用第三方/生成报表...) -> Response
^ 耗时 & 易失败 & 不可控

异步 + MQ(改造模型)

1
2
3
4
Client -> Web Server -> 把任务丢进 MQ -> Response(立刻返回)
|
v
Worker 从 MQ 取任务 -> 执行 -> 记录结果/重试

3.2 Mermaid 流程图:任务的最小生命周期(概念版)

flowchart LR
    A[Client 请求] --> B[Web 服务/Producer]
    B -->|Send Task Message| C[(Message Broker / RabbitMQ)]
    C -->|Consume| D[Worker/Consumer]
    D --> E[执行任务逻辑]
    E --> F[(结果存储 可选)]

这里你先记住一句话:

MQ 的核心是“存下来再说”,Worker 的核心是“取出来去做”。

3.3 为什么 MQ 能解决那些灾难?

  • 解耦:Web 不关心任务怎么执行、执行多久、失败怎么办
  • 削峰:流量瞬间 10 万请求,队列先堆住,Worker 慢慢消化
  • 可靠性:任务可以持久化,Worker 宕机后能恢复继续处理
  • 可扩展:任务多了就加 Worker,不必扩 Web

4️⃣ 与 RabbitMQ / Celery 的关系

这一章你只需要建立“分工边界”的直觉:

RabbitMQ(Broker 的一种实现)

  • 负责:可靠地收消息、存消息、按规则投递消息
  • 不负责:执行任务逻辑

Celery(分布式任务框架)

  • 负责:把函数变成任务、把任务投递给 Broker、Worker 拉取执行、管理重试/并发/路由/结果
  • 不负责:实现消息队列(它依赖 RabbitMQ/Redis 等 Broker)

所以 RabbitMQ + Celery 的经典组合就是:

1
Celery Producer  ->  RabbitMQ(Broker)  ->  Celery Workers

RabbitMQ 是“任务的物流系统”,Celery 是“任务的生产线管理系统”。


5️⃣ 最小可运行示例(Practice)

这一章的“最小示例”目标不是让你立刻跑 RabbitMQ(第 2 章开始我们会系统跑起来),
而是让你先形成异步任务拆分的代码形态主流程只负责投递,耗时逻辑放到任务函数里

5.1 目录结构(先记住这个形状)

1
2
3
proj/
app.py # 模拟 Web 入口:只负责投递任务
tasks.py # 任务定义:耗时逻辑在这里

5.2 tasks.py:把“耗时逻辑”封装成任务函数

1
2
3
4
5
6
7
# tasks.py
import time

def send_welcome_email(user_id: int):
# 假装这是一个耗时、可能失败的外部操作
time.sleep(3)
print(f"[task] welcome email sent to user={user_id}")

5.3 app.py:主流程“只投递,不等待”(概念版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# app.py
from tasks import send_welcome_email

def register(user_id: int):
# 1) 核心同步逻辑:注册成功(必须立刻完成)
print(f"[web] user {user_id} registered")

# 2) 非核心逻辑:欢迎邮件(应该异步)
# 现在我们先用“假装投递”的方式表达意图:
print("[web] enqueue task: send_welcome_email")
# 在 Celery 中,这里会变成 delay()/apply_async()
# send_welcome_email.delay(user_id)

# 3) 立刻返回给用户
return {"ok": True}

if __name__ == "__main__":
print(register(42))

5.4 每一部分在系统中的作用(非常重要)

  • register():主链路(同步),必须快、必须稳
  • send_welcome_email():异步任务,慢也没关系,失败可重试
  • “enqueue task”:未来用 RabbitMQ + Celery 实现真正投递

新手常见误区(提前点名)

  1. 把所有事情都异步化
    • 核心一致性逻辑(比如扣库存、支付确认)乱异步会导致业务错乱
  2. 异步 = 不可靠
    • 实际上设计得当,异步系统可以比同步更可靠(因为可重试、可恢复)
  3. 以为 MQ 只是性能工具
    • 更重要的是“隔离故障 + 解耦依赖”,性能只是副产品

6️⃣ 📒 本章笔记总结

  • 异步任务要解决的核心矛盾:主链路必须短、快、稳;非关键工作耗时且易失败
  • 消息队列(MQ)的核心价值:
    • 解耦:生产者不依赖消费者的执行细节
    • 削峰:突发流量先堆队列,慢慢消费
    • 可靠传递:消息可持久化,宕机可恢复
    • 水平扩展:任务多就加 worker
  • 关键角色:Producer / Broker / Worker / Message(Task)
  • RabbitMQ vs Celery 分工:
    • RabbitMQ:负责“收、存、投递消息”
    • Celery:负责“把函数任务化、投递、消费执行、并发/重试/路由/结果管理”
  • 工程原则:同步做必须立刻完成的,异步做可以延迟且可重试的

7️⃣ 📒 异步任务 & MQ 问题总结

一、Web 框架已经是 async 了,为什么还需要 MQ?

❓问题本质

FastAPI / asyncio 已经是异步并发了,为什么还会有“阻塞 / 等待 / 雪崩”的问题?

✅ 核心结论

  • Web async ≠ 异步任务系统
  • Web async 解决的是:IO 等待不阻塞线程
  • MQ + Worker 解决的是:系统级解耦、资源隔离、稳定性

📌 关键区别

维度 Web async MQ + Worker
执行位置 Web 进程内 独立进程 / 机器
CPU 密集
外部依赖抖动 ❌ 容易拖垮 ✅ 被隔离
限速 / 削峰
失败重试

二、异步任务丢进 MQ 就返回了,结果什么时候知道?

❓问题本质

没有同步返回结果,那任务“完成”的信号在哪里?

✅ 三种主流结果模型

1️⃣ Fire-and-Forget(最常见)

  • 不关心结果
  • 如:发邮件 / 发短信 / 同步数据

2️⃣ 轮询 / 查询结果

  • 提交任务 → 返回 task_id
  • 前端 / 调用方轮询 DB / Redis / Celery backend

3️⃣ 推送 / 回调(高级)

  • WebSocket / SSE / 回调接口
  • 少数强实时场景使用

📌 工程经验

90% 任务不需要立刻知道结果
异步系统天然是“最终一致性”


三、多长时间 / 多大并发的任务适合用 MQ?

❌ 错误判断方式

  • “超过 1s 才用 MQ”
  • “必须是 100s 才值得”

✅ 正确判断维度(是否用 MQ)

判断点 如果是
是否阻塞 Web 主链路 用 MQ
是否调用外部系统 用 MQ
是否可能失败重试 用 MQ
是否可能突发流量 用 MQ
是否不希望跑在 Web 进程 用 MQ

📌 黄金法则(必记)

凡是你不希望跑在 Web 进程里的逻辑,都应该进任务队列


四、RabbitMQ 可以 Docker 跑吗?能给多个应用用吗?

✅ Docker 运行 RabbitMQ

  • 完全支持
  • 生产环境常态
1
rabbitmq:3-management
  • 5672:业务连接
  • 15672:管理后台

✅ 一个 MQ 服务多个应用(标准架构)

  • 使用 Virtual Host / Exchange / Queue 隔离
  • 权限、命名、路由解耦
1
2
3
4
一个 RabbitMQ 集群
├─ App A(vhost /a)
├─ App B(vhost /b)
└─ App C(vhost /c)

五、Celery 支持任务嵌套 / 长任务拆分吗?

✅ 支持,而且是核心能力

Celery 不推荐:

  • 在任务里 get() 等子任务结果(❌ 会阻塞 worker)

Celery 推荐:

  • 任务编排(Canvas)

常用编排方式

方式 场景
chain 串行步骤依赖
group 并行子任务
chord 并行 + 汇总
动态生成任务 扇出数量运行时决定

📌 工程原则

  • 长任务 → 拆阶段
  • 可并行 → group / chord
  • 不要在 worker 里“同步等待”


【第 2 章:RabbitMQ 是什么?核心思想与工作模型】

这一章的目标只有一个:
把 RabbitMQ 从“一个黑盒中间件”拆成你脑子里能运转的模型
看完这一章,你应该能回答:
👉 “一条消息进来后,RabbitMQ 到底对它做了什么?”


1️⃣ 本章要解决什么问题(Why)

在第 1 章我们已经知道:
MQ 是为了解耦生产者和消费者、保护系统稳定性而存在的

但这会立刻引出 3 个工程问题:

  1. 消息进来后放哪?
  2. 有多个消费者时,消息怎么分发?
  3. 不同类型的消息,怎么路由到不同处理逻辑?

如果 MQ 只是一个简单队列(FIFO):

  • 所有消息挤在一起
  • 所有消费者只能“抢”
  • 无法区分任务类型
  • 扩展性和可维护性极差

RabbitMQ 诞生的核心动机就是:

把“消息存储”和“消息路由”彻底解耦,让消息投递规则可配置、可扩展。


2️⃣ 核心概念与角色(What)

这一章你先记住 4 个角色,它们构成 RabbitMQ 的最小心智模型:

角色 本质作用
Producer 发送消息的应用
Exchange 消息路由器(不存消息)
Queue 真正存消息的地方
Consumer 从 Queue 取消息处理

⚠️ 非常重要的一句话

Producer 从不直接把消息发给 Queue,而是发给 Exchange

这是 RabbitMQ 和“朴素队列”的根本区别。


3️⃣ 工作原理(How)

我们一步一步拆解“一条消息的完整旅程”。


3.1 最小消息流(核心路径)

flowchart LR
    P[Producer] -->|publish| E[Exchange]
    E -->|route| Q[Queue]
    Q -->|consume| C[Consumer]

注意职责边界:

  • Producer:只管发消息,不知道 Queue 存在哪
  • Exchange:只负责路由决策
  • Queue:只负责存消息
  • Consumer:只管取消息执行

3.2 为什么一定要有 Exchange?

假设没有 Exchange(Producer 直连 Queue):

  • Producer 必须知道所有 Queue
  • 新增消费者要改 Producer 代码
  • 路由逻辑散落在业务代码里

有了 Exchange:

  • Producer 只面向“规则”
  • 路由策略集中管理
  • 系统更像“可配置基础设施”

📌 工程视角

Exchange 是 RabbitMQ 的“控制平面”,Queue 是“数据平面”。


3.3 Exchange 的核心工作:路由(不是存储)

Exchange 做的只有一件事:

根据“规则”,把消息复制 / 投递到 0~N 个 Queue

Exchange 不存消息,消息一旦路由完成,就和 Exchange 无关了。


4️⃣ 与 Celery 的关系

这一章你要在脑子里建立这张映射表:

RabbitMQ Celery 中的角色
Producer task.delay() / apply_async()
Exchange Celery 自动创建的 task exchange
Queue Celery 的任务队列
Consumer Celery worker

当你写:

1
send_email.delay(user_id)

本质发生的是:

1
2
3
4
Celery Producer
-> publish 消息到 Exchange
-> Exchange 路由到 Queue
-> Worker 从 Queue 取任务

📌 关键认知

Celery 把 RabbitMQ 当作“可编程任务路由器”来用。


5️⃣ 📒 本章笔记总结

  • RabbitMQ 的核心思想:消息存储 ≠ 消息路由
  • Producer 永远把消息发给 Exchange,而不是 Queue
  • Exchange 不存消息,只做路由决策
  • Queue 是消息的唯一存储单元
  • Consumer 只从 Queue 拉消息
  • RabbitMQ 通过 Exchange 实现“配置化路由”
  • Celery 利用 RabbitMQ 的 Exchange/Queue 机制完成任务分发

6️⃣ 📒 第 2 章相关疑问

Q1:pika 是什么?和 Celery 什么关系?

  • pika:Python 的 RabbitMQ(AMQP)客户端库,偏底层。
    • 能做:连接 RabbitMQ、声明 exchange/queue/binding、publish/consume、ack 等。
  • Celery:分布式任务框架(任务定义、投递、worker 并发、重试、路由、结果等一整套)。
  • 两者关系
    • 用 pika:你在“手写消息通信细节”(更底层、学习 RabbitMQ 原理很适合)。
    • 用 Celery:Celery 通过自己的消息通信抽象(通常是 kombu)来和 RabbitMQ/Redis 等 broker 通信;一般不需要你直接写 pika
  • 工程结论
    • 学原理 → pika 很好
    • 落地任务系统 → 直接 Celery

Q2:除了 RabbitMQ,还有什么消息队列?

按“模型/适用场景”分两类更好记:

A 类:传统消息队列(偏任务分发/路由/ACK)

  • RabbitMQ(AMQP 模型,路由能力强)
  • ActiveMQ / Artemis(JMS 生态)
  • NATS / JetStream(轻量高速)
  • Redis(常用作轻量 broker,但可靠性/语义边界要清楚)

B 类:事件流/日志平台(偏高吞吐、可回放、流处理)

  • Kafka(吞吐高、分区顺序、可回放)
  • Pulsar(类似 Kafka,但多租户/存储分层等特性不同)

选型速记

  • “任务队列 + 路由 + 重试/ACK” → RabbitMQ 常见
  • “海量事件流 + 回放 + 数据管道” → Kafka/Pulsar
  • “轻量、好部署、快速落地” → Redis(需明确可靠性策略)

Q3:Exchange 和 Queue 都是 RabbitMQ 的能力吗?

  • :在 RabbitMQ(以及 AMQP 风格模型)里,Exchange/Queue/Binding 是核心抽象。
  • 但要更精确:
    • Queue:几乎所有 MQ 都有“存消息的地方”(名字可能叫 queue/topic/stream)。
    • Exchange:更像 RabbitMQ/AMQP 的标志性“路由层抽象”,不是所有 MQ 都有同名概念(例如 Kafka 没有 Exchange)。

Q4:AMQP 是什么?

  • AMQP(Advanced Message Queuing Protocol):一种消息通信协议/规范

  • 定义了:客户端如何与 broker 通信,包括

    • connection/channel
    • publish/consume
    • ack/nack
    • 声明与绑定(exchange/queue/binding 的交互语义)
  • 一句话

    AMQP 是“消息中间件的协议与语义标准”,RabbitMQ 是其典型实现之一。

  • pika 本质上就是在“讲 AMQP 这门协议”。


Q5:如果使用 Redis,当 broker 时就不存在 Exchange 吗?

  • 基本结论:对,Redis broker 通常没有 RabbitMQ 那种 Exchange 路由层。
  • RabbitMQ:路由在 broker 内(Exchange 决策,支持 direct/topic/fanout 等)
  • Redis:更常见是 Producer 直接写入某个队列 key(List/Stream),
    “路由”往往变成:你(或 Celery)选择写到哪个队列名/哪个 key
  • 工程差异速记
    • RabbitMQ:路由能力强、规则集中、天然支持广播/复杂匹配
    • Redis:部署简单、轻量,但复杂路由更多在应用/Celery 侧实

【第 3 章:RabbitMQ 核心组件详解(Exchange / Queue / Binding)】

1️⃣ 本章要解决什么问题(Why)

在第 2 章你已经有了最小心智模型:

Producer → Exchange → Queue → Consumer

但只知道“有 Exchange/Queue”还不够,上工程会马上遇到这些问题:

  1. 一条消息应该进哪个队列?(按任务类型/业务域隔离)
  2. 一个消息要不要发给多个队列?(广播、同时触发多种处理)
  3. 如何做到“规则可配置”而不是写死在代码里?
  4. Celery 说的 routing_key / queue / exchange 到底对应 RabbitMQ 的什么?

如果没有 Binding 这套机制,你就只能让 Producer 直接指定队列名(强耦合),系统会迅速变成“改一个队列,所有服务改代码”。

RabbitMQ 的解法是:

用 Exchange 做路由入口,用 Binding 把“路由规则”配置化,把消息送入一个或多个 Queue。


2️⃣ 核心概念与角色(What)

2.1 三大组件定义(最关键)

  • Exchange(交换机):消息的“路由器”,决定消息去哪些队列
    • ✅ 做路由
    • ❌ 不存消息
  • Queue(队列):消息的“存储容器”,消费者从这里取
    • ✅ 存消息(内存/磁盘,取决于持久化设置)
    • ✅ 支持消费者竞争消费/并行扩展
  • Binding(绑定):把 Exchange 和 Queue 连起来的“路由规则”
    • 它本质上是一条规则:
      (exchange, binding_key) → queue

2.2 角色关系说明(把图刻进脑子)

flowchart LR
    P[Producer 生产者] -->|publish + routing_key| E[Exchange 交换机]
    E -->|match: routing_key vs binding_key| Q1[Queue A]
    E -->|match: routing_key vs binding_key| Q2[Queue B]
    E -->|match: routing_key vs binding_key| Q3[Queue C]
    Q1 -->|consume| C1[Consumer/Worker A]
    Q2 -->|consume| C2[Consumer/Worker B]
    Q3 -->|consume| C3[Consumer/Worker C]

读法:生产者发布到 Exchange → Exchange 根据匹配规则把消息路由到一个或多个 Queue → 消费者从 Queue 拉取处理

这里有 2 个“Key”新手最容易混:

  • routing_key:Producer 发消息时带的“标签”
  • binding_key:Queue 绑定到 Exchange 时声明的“匹配规则”

📌 核心句(必背)

routing_key 是消息的“地址标签”,binding_key 是队列的“接收规则”。
Exchange 做的事:拿 routing_key 去匹配 binding_key。


3️⃣ 工作原理(How)

RabbitMQ 的路由能力主要由 Exchange 类型决定。你必须掌握四种类型:

  • direct
  • fanout
  • topic
  • headers(较少用)

我用同一套“邮件/短信/日志”例子带你理解。


3.1 direct:精确匹配(最像”定向投递”)

规则routing_key == binding_key 才投递

flowchart LR
  P[Producer rk=task.email] --> E[direct exchange]
  E -->|binding_key=task.email| Q1[email-queue]
  E -->|binding_key=task.sms| Q2[sms-queue]
  • task.email → 进 email-queue
  • task.sms → 进 sms-queue

✅ 适合:任务类型明确、队列划分明确(Celery 常用)


3.2 fanout:广播(最像”群发”)

规则:忽略 routing_key,把消息复制到所有绑定队列

flowchart LR
  P[Producer] --> E[fanout exchange]
  E --> Q1[queue-A]
  E --> Q2[queue-B]
  E --> Q3[queue-C]

✅ 适合:

  • 配置变更广播
  • 缓存失效通知
  • “所有下游都要知道的事件”

⚠️ 误区:

  • fanout 会让消息量 队列数倍增,要评估成本

3.3 topic:通配符匹配(最强但要规范)

topic 是 RabbitMQ 工程里最常用也最容易用乱的。

规则routing_key. 分段,binding_key 支持通配符:

  • * 匹配 恰好 1 段
  • # 匹配 0~多段

例子:

  • routing_key:task.email.send
  • binding_key:
    • task.*.send ✅(匹配 task + 1段 + send)
    • task.# ✅(匹配以 task 开头的任何)
    • task.email.* ✅(匹配 task.email 下 1 段)
flowchart LR
  P[Producer rk=task.email.send] --> E[topic exchange]
  E -->|binding_key=task.email.*| Q1[email-queue]
  E -->|binding_key=task.#| Q2[all-task-queue]

✅ 适合:业务域分层路由(系统变大后很好用)

⚠️ 新手常见误区:

  • routing_key 命名没规范,topic 立刻变“玄学路由”
  • 建议统一:域.子域.动作.版本业务.模块.事件

3.4 headers:按消息头匹配(很少用)

规则:不看 routing_key,看 message headers(键值对)

✅ 适合:需要复杂条件组合,但一般 topic 已足够
⚠️ 复杂度高、可读性差,工程中不常见


3.5 一条消息能去哪?(0~N 个队列)

RabbitMQ 路由结果可能是:

  • 0 个队列:消息被丢弃或触发 Return(看 mandatory 等设置)
  • 1 个队列:最常见
  • N 个队列:广播/多订阅者模型
flowchart LR
  P --> E[Exchange]
  E --> Q1
  E --> Q2
  E --> Q3

4️⃣ 与 RabbitMQ / Celery 的关系

这一节只讲“映射关系”,让你不再被术语绕晕。

4.1 Celery 的”路由”对应 RabbitMQ 的什么?

当你在 Celery 里配置:

  • task_routes / queue / routing_key / exchange

对应 RabbitMQ:

  • Celery Producer 发布消息到某个 Exchange
  • 并带一个 routing_key
  • RabbitMQ 用 binding 规则把消息送到指定 Queue
  • Worker 监听对应 Queue

你可以把 Celery 的任务路由理解为:

Celery 帮你生成 routing_key,并帮你声明 Exchange/Queue/Binding。

4.2 为什么 Celery 常用 direct/topic,而不是 fanout?

  • 任务队列的核心诉求:一个任务只应该被一个 worker 消费(通常)
  • fanout 会复制消息给多个队列,导致同一个任务可能被多方处理(除非你明确要广播)
  • direct/topic 更符合“按类型路由到单队列,再由队列内部竞争消费”的模式

📌 工程经验

任务分发:direct/topic
事件广播:fanout

sequenceDiagram
  participant P as Producer
  participant E as Exchange
  participant B as Bindings(规则表)
  participant Q as Queue(s)

  P->>E: publish(message, routing_key)
  E->>B: 查询所有绑定规则
  B-->>E: 返回 (queue, binding_key) 列表
  E->>E: 按 exchange_type 执行匹配/路由
  E->>Q: 投递到匹配成功的 0~N 个队列

5️⃣ 📒 本章笔记总结

  • RabbitMQ 三核心组件:
    • Exchange:路由器,不存消息
    • Queue:存消息,消费者从这里取
    • Binding:路由规则,把 Exchange 和 Queue 连接起来
  • 两个 Key:
    • routing_key(消息标签):Producer 发布消息时携带
    • binding_key(接收规则):Queue 绑定 Exchange 时声明
    • Exchange 用 routing_key 匹配 binding_key 决定投递队列
  • Exchange 四类型:
    • direct:精确匹配(rk==bk)
    • fanout:广播(忽略 rk)
    • topic:通配符匹配(* 一段,# 多段)
    • headers:按消息头匹配(少用)
  • 一条消息可投递到 0~N 个队列(取决于绑定规则)
  • 与 Celery 的关系:Celery 通过配置生成 exchange/queue/routing_key,并由 RabbitMQ 完成最终路由投递
  • 工程建议:
    • 任务分发优先 direct/topic
    • 事件广播才用 fanout
    • topic 必须先定 routing_key 命名规范,否则会变“玄学路由”



本站总访问量 加载中...
今日访客数 加载中...
本页总访客数 加载中...
发表了 36 篇文章 🔸 总计 111.4k 字