RabbitMQ: 消息监听机制深度解析之从 SimpleMessageListenerContainer 到工程实现

kafka原理和实践(四)spring-kafka消费者源码 目录一、kafkaConsumer消费者模型一、kafkaConsumer构造二、消费者容器启动流程 正文系列目录 kafka原理和实践(一)原理:10分钟入门 kafka原理和实践(二)spring-kafka简单实践 kafka原理和实践(三)spring-kafka生产者源码 kafka原理和实践(四)spring-kafka消费者源码 kafka原理和实践(五)spring-kafka配置... 阅读详情

核心原理剖析


核心原理:生命周期驱动+双队列缓冲+异步消费模型

1 ) 容器生命周期与启动流程

  • 继承关系:SimpleMessageListenerContainer → AbstractMessageListenerContainer → RabbitAccessor + 实现 MessageListenerContainer 接口
  • 关键接口:
    • SmartLifecycle → Lifecycle(Spring 框架核心接口)
    • start() 方法:容器初始化时由 Spring 自动调用,触发消息监听。
  • 启动链:
    start() → doStart() → initializeConsumers() → createBlockingQueueConsumer()  
    

2 ) 消费者封装:BlockingQueueConsumer

  • 核心作用:封装与 RabbitMQ Broker 的通信细节(Connection/Channel),管理独立生命周期。
  • 消息接收逻辑:
    • 初始化时调用 channel.basicConsume(),注册回调函数 InternalConsumer.handleDelivery()。
    • 收到消息后,压入内存阻塞队列(BlockingQueue)缓冲。
      // 伪代码:消息接收回调  
      class InternalConsumer {  
        void handleDelivery(...) {  
          queue.offer(message); // 消息入队  
        }  
      }  
      

3 ) 消息处理循环

  • 异步处理器:AsyncMessageProcessingConsumer(实现 Runnable 接口)
    • 由线程池执行,循环调用 mainLoop() → receiveAndExecute()。
  • 消息消费流程:
    receiveAndExecute() → doReceiveAndExecute() → consumer.nextMessage()  
    → queue.poll() // 从本地队列取出消息  
    → executeListener() // 触发业务逻辑  
    
  • 最终回调:通过反射调用开发者定义的 onMessage() 方法。

关键设计思想


  1. 双队列缓冲机制:
    • RabbitMQ 队列 → 内存阻塞队列 → 业务消费线程,解耦消息接收与处理。
  2. 资源隔离:
    • 每个 BlockingQueueConsumer 独占 TCP 连接(Connection)和信道(Channel),避免竞争。
  3. 线程模型:
    • IO 线程:处理 AMQP 协议通信(非阻塞模式)。
    • 业务线程池:执行 onMessage() 逻辑,与 IO 线程分离。

监听容器生命周期管理机制


  1. 容器启动流程
// 等效NestJS实现:自定义监听容器  
import { Injectable, OnApplicationBootstrap } from '@nestjs/common';  
import { connect, Channel, Connection } from 'amqplib';  
 
@Injectable()  
export class RabbitMQContainer implements OnApplicationBootstrap {  
  private connection: Connection;  
  private channel: Channel;  
  private blockingQueueConsumer: BlockingQueueConsumer;  
 
  async onApplicationBootstrap() { // 等效Spring的Lifecycle.start()  
    await this.initialize();  
    await this.blockingQueueConsumer.start();  
  }  
 
  private async initialize() {  
    this.connection = await connect('amqp://localhost');  
    this.channel = await this.connection.createChannel();  
    this.blockingQueueConsumer = new BlockingQueueConsumer(this.channel);  
  }  
}  
  1. 核心接口映射表
    | Spring接口 | NestJS等效实现 | 核心方法 |
    |---------------------|----------------------------|----------------------|
    | SmartLifecycle | OnApplicationBootstrap | onApplicationBootstrap() |
    | MessageListener | MessageHandler | handleMessage() |

双队列消息处理架构


RabbitMQ层 -> 本地内存缓冲队列 -> 业务处理器

// 核心缓冲队列实现  
import { ConsumeMessage } from 'amqplib';  
 
class BlockingQueueConsumer {  
  private readonly queue: ConsumeMessage[] = [];  
  private maxBufferSize = 100;  
 
  // 消息接收:RabbitMQ回调入口  
  handleDelivery(msg: ConsumeMessage) {  
    if (this.queue.length < this.maxBufferSize) {  
      this.queue.push(msg); // 消息暂存缓冲队列  
    }  
  }  
 
  // 消息取出:供消费线程调用  
  nextMessage(): ConsumeMessage | null {  
    return this.queue.shift() || null;  
  }  
}  

异步消费线程模型


消息处理流程

// NestJS等效的异步处理器  
import { Process, Processor } from '@nestjs/bull';  
import { Job } from 'bull';  
 
@Processor('message-queue')  
export class MessageConsumer {  
 
  @Process()  
  async processMessage(job: Job<ConsumeMessage>) {  
    const msg = job.data;  
    console.log(`[消费消息] ${msg.content.toString()}`);  
    // 执行业务逻辑...  
  }  
}  

线程协作关系

  1. IO线程:通过channel.basicConsume()接收消息 → 存入内存队列
  2. 工作线程:从内存队列取消息 → 调用handleMessage()执行业务逻辑

工程示例:1


1 ) 方案 1:基础监听器实现

// src/rabbitmq/rabbitmq.service.ts  
import { Injectable, OnModuleInit } from '@nestjs/common';  
import { connect, Channel, Connection, ConsumeMessage } from 'amqplib';  
 
@Injectable()  
export class RabbitMQService implements OnModuleInit {  
  private connection: Connection;  
  private channel: Channel;  
 
  async onModuleInit() {  
    this.connection = await connect('amqp://localhost');  
    this.channel = await this.connection.createChannel();  
    await this.setupConsumer();  
  }  
 
  private async setupConsumer() {  
    const QUEUE_NAME = 'nestjs_queue';  
    await this.channel.assertQueue(QUEUE_NAME, { durable: true });  
 
    // 核心:注册消息回调  
    this.channel.consume(QUEUE_NAME, (msg: ConsumeMessage | null) => {  
      if (msg) {  
        this.handleMessage(msg);  
        this.channel.ack(msg); // 手动确认  
      }  
    }, { noAck: false });  
  }  
 
  private handleMessage(msg: ConsumeMessage) {  
    const content = msg.content.toString();  
    console.log(`[NestJS] Received: ${content}`);  
    // 业务逻辑处理  
  }  
}  

2 ) 方案 2:使用 @golevelup/nestjs-rabbitmq 高级封装

// src/rabbitmq/rabbit.module.ts  
import { Module } from '@nestjs/common';  
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';  
 
@Module({  
  imports: [  
    RabbitMQModule.forRoot(RabbitMQModule, {  
      exchanges: [{ name: 'nestjs_exchange', type: 'direct' }],  
      uri: 'amqp://localhost',  
      connectionInitOptions: { wait: true },  
    }),  
  ],  
  providers: [/* Services */],  
})  
export class RabbitModule {}  
 
// src/consumer/consumer.service.ts  
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';  
 
@Injectable()  
export class ConsumerService {  
  @RabbitSubscribe({  
    exchange: 'nestjs_exchange',  
    routingKey: 'demo_routing_key',  
    queue: 'nestjs_queue',  
  })  
  handleMessage(msg: {}) {  
    console.log(`[Subscriber] Received: ${JSON.stringify(msg)}`);  
  }  
}  

3 ) 方案 3:自定义装饰器 + 消息确认控制

// src/decorators/rabbit-handler.decorator.ts  
import { SetMetadata } from '@nestjs/common';  
export const RABBIT_HANDLER = 'RABBIT_HANDLER';  
export const RabbitHandler = (queue: string) =>  
  SetMetadata(RABBIT_HANDLER, queue);  
 
// src/rabbitmq/consumer.service.ts  
import { Channel, ConsumeMessage } from 'amqplib';  
 
@Injectable()  
export class ConsumerService {  
  constructor(private readonly channel: Channel) {}  
 
  @RabbitHandler('custom_queue')  
  async processMessage(msg: ConsumeMessage) {  
    try {  
      const data = JSON.parse(msg.content.toString());  
      await this.businessLogic(data);  
      this.channel.ack(msg); // 业务成功时确认  
    } catch (error) {  
      this.channel.nack(msg, false, true); // 重试  
    }  
  }  
}  

工程示例:2


1 ) 方案1:原生amqplib实现

// src/rabbitmq/rabbit.service.ts  
import { Injectable, OnModuleInit } from '@nestjs/common';  
import { connect, Channel, Connection, ConsumeMessage } from 'amqplib';  
 
@Injectable()  
export class RabbitService implements OnModuleInit {  
  private conn: Connection;  
  private channel: Channel;  
  private readonly LOCAL_QUEUE: ConsumeMessage[] = [];  
 
  async onModuleInit() {  
    this.conn = await connect('amqp://localhost');  
    this.channel = await this.conn.createChannel();  
    await this.channel.assertQueue('order_queue');  
 
    // RabbitMQ原生监听  
    this.channel.consume('order_queue', (msg) => {  
      if (msg) this.LOCAL_QUEUE.push(msg);  
    });  
 
    // 启动消费线程  
    this.startConsumerThread();  
  }  
 
  private startConsumerThread() {  
    setInterval(() => {  
      const msg = this.LOCAL_QUEUE.shift();  
      if (msg) {  
        console.log('处理消息:', msg.content.toString());  
        this.channel.ack(msg);  
      }  
    }, 100); // 每100ms处理一次  
  }  
}  

2 ) 方案2:@nestjs/microservices集成方案

// src/main.ts  
import { NestFactory } from '@nestjs/core';  
import { Transport } from '@nestjs/microservices';  
 
async function bootstrap() {  
  const app = await NestFactory.createMicroservice(AppModule, {  
    transport: Transport.RMQ,  
    options: {  
      urls: ['amqp://localhost'],  
      queue: 'payment_queue',  
      queueOptions: { durable: true },  
      // 核心参数  
      prefetchCount: 10, // 每次取10条消息缓冲  
    },  
  });  
  await app.listen();  
}  
bootstrap();  

3 ) 方案3:自定义装饰器实现

// src/decorators/rabbit-listener.decorator.ts  
import { createParamDecorator } from '@nestjs/common';  
 
export const RabbitListener = createParamDecorator((queue: string) => {  
  return (target, key, descriptor) => {  
    const originalMethod = descriptor.value;  
    // 模拟消息分发逻辑  
    descriptor.value = async (msg: any) => {  
      console.log(`[${queue}] 收到消息`, msg);  
      return originalMethod.call(target, msg);  
    };  
    return descriptor;  
  };  
});  
 
// 控制器使用示例  
@Controller()  
export class OrderController {  
  @RabbitListener('order_created')  
  handleOrderCreated(@Payload() data: OrderDto) {  
    // 业务处理逻辑  
  }  
}  

RabbitMQ关键命令与配置


1 ) 队列声明

// 创建持久化队列  
await this.channel.assertQueue('task_queue', {  
  durable: true,  
  deadLetterExchange: 'dlx_exchange' // 死信交换机  
});  

2 ) 消费端参数配置

参数作用NestJS配置项
prefetchCount每次预取消息数量options.prefetchCount
noAck关闭自动ACKoptions.noAck = false
consumerTag消费者标识options.consumerTag

关键技术细节解析


  1. 双缓冲队列价值

    • 解耦IO与处理:防止RabbitMQ消息洪水冲击业务线程
    • 流量整形:通过maxBufferSize控制内存积压量
    • 优先级处理:可在本地队列实现消息优先级排序
  2. 消费线程模型对比

    类型优势适用场景
    单线程轮询资源占用少低吞吐场景
    Worker线程池并行处理(nestjs-bull)高并发业务
  3. 异常处理关键点

    // 消息处理异常捕获  
    try {  
      await businessHandler(msg);  
      this.channel.ack(msg);  
    } catch (err) {  
      this.channel.nack(msg, false, true); // 重试  
      // 或 this.channel.reject(msg, false); // 进入死信队列  
    }  
    

常见问题解决方案


问题原因修复方案
消息重复消费未及时 ACK 或网络抖动实现幂等处理 + 手动 ACK 超时设置
连接频繁断开心跳超时调整 heartbeat 参数 ≥ 30s
内存队列堆积业务处理阻塞增加消费者线程池大小

设计启示:NestJS 中优先使用 @golevelup/nestjs-rabbitmq 库,其内部封装了类似 SimpleMessageListenerContainer 的 自动重连、线程池管理 等机制,避免重复造轮子

其他:

  1. 消息积压

    • 动态调整prefetchCount:基于系统负载自动扩缩容
      // 动态调整示例  
      setInterval(() => {  
        const load = getSystemLoad();  
        const newPrefetch = load > 80 ? 5 : 20;  
        this.channel.prefetch(newPrefetch);  
      }, 5000);  
      
  2. 消息丢失防护

    • 开启生产者确认模式(publisher confirm)
    • 启用队列镜像(Mirrored Queues)
      # RabbitMQ策略配置  
      rabbitmqctl set_policy ha-queues ".*" '{"ha-mode":"all"}'  
      
  3. 消费者宕机处理

    // 实现消费者心跳检测  
    this.channel.on('close', (err) => {  
      console.error('连接断开,尝试重连...');  
      this.reconnect(); // 自动重连机制  
    });  
    

注意细节:

  1. 生命周期钩子是驱动监听的核心(onApplicationBootstrap)
  2. 双队列设计是高性能消费的关键架构
  3. 消息确认机制必须与业务异常处理深度集成
  4. NestJS生态中优先选择@nestjs/microservices官方方案

关键配置与运维实践


  1. 连接优化:
    // 心跳检测与重连  
    const connection = await connect({  
      protocol: 'amqp',  
      hostname: 'localhost',  
      heartbeat: 60, // 60秒心跳  
      reconnect: true,  
    });  
    
  2. QoS 控制:
    await channel.prefetch(10); // 单信道最大未确认消息数  
    
  3. 死信队列:
    await channel.assertQueue('dlq', { durable: true });  
    await channel.bindQueue('dlq', 'dlx_exchange', '#');  
    
Spring整合ActiveMQ之监听器配置 简介 在EJB世界里,JMS消息最常用的功能之一是用于实现消息驱动Bean(MDB)。Spring提供了一个方法来创建消息驱动的POJO(MDP),并且不会把用户绑定在某个EJB容器上。 通常用消息监听器容器从JMS消息队列接收消息并驱动被注射进来的MDP。消息监听器容器负责消息接收的多线程处理并分发到各MDP中。一个消息侦听容器是MDP和消息提供者之间的一个中介,用来处理消息接收的注册,事务... 阅读详情

相关推荐

Springboot RabbitMq源码解析之消费者容器SimpleMessageListenerContainer

四、 消息接收与处理 1. SimpleMessageListenerContainer#receiveAndExecute private boolean receiveAndExecute(final BlockingQueueConsumer consumer) throws Throwable { if (getTransactionManager() != null) { try...

翻身已碰头的博客 2万+

RabbitMQ中动态创建队列和监听

因为运输平台很多,最终可能有1000家左右,每个平台又有10个左右的报文,而且处理每个报文需要创建4个队列,那么就需要在producer和consumer中的config文件中写 4x10x1000个交换机、队列绑定、@Listener。即使做了代码优化,也需要大量的声明代码需要写。因为前期进行联调测试的时候,可能只有几家,但是慢慢地会有越来越多的平台接进来,那么每有一家接入的话,就需要改动代码,这样,改动地太频繁容易出现出错、不易于版本管理,而且对于已经联调通过的平台,可能会存在一些逻辑上的干扰。

tianjiliuhen的博客 2万+

SpringRabbitMQ消息接收:SimpleMessageListenerContainer配置

SimpleMessageListenerContainer作为Spring AMQP中的核心组件,为RabbitMQ消息接收提供了强大而灵活的支持。通过合理配置并发参数和预取值,可以充分利用系统资源提升消息处理性能;通过设置适当的确认模式和异常处理策略,可以保障消息处理的可靠性;结合消息转换器,可以简化消息反序列化过程,使开发者专注于业务逻辑实现;而高级特性的应用则进一步增强了系统在生产环境中的稳定性和可靠性。

程序媛学姐的博客 997

springboot-rabbitmq 实现动态配置监听容器

springboot-rabbitmq 实现动态配置监听容器

m0_46978151的博客 6177

王道考研计算机网络课件全解析:从零搭建你的知识框架(附B站视频+PPT下载)

本文全面解析王道考研计算机网络课程的核心知识体系,从OSI七层模型到TCP/IP四层架构,详细讲解物理层、数据链路层、网络层、传输层和应用层的关键技术。提供高效学习路径、实战案例和常见误区警示,帮助考生系统构建计算机网络知识框架,适合计算机考研备考者参考学习。

leaf8的博客 568

100kHz手持LCR表设计:高频阻抗测量与校准实战解析

阻抗测量是电子元件测试的基础,LCR表通过施加正弦激励并分析电压电流的幅值与相位,即可获取电容、电感、电阻的等效参数。在开关电源、无线充电等高频应用场景中,元件在100kHz下的ESR和阻抗特性尤为关键,传统10kHz手持仪表难以满足需求。本文从自动平衡桥原理出发,探讨如何将台式电桥的核心能力浓缩至手持设备,重点剖析高频寄生参数抑制、模拟链路设计、数字相敏检波算法及开路短路校准方法。通过实测数据验证,在100kHz频率下基本精度可达0.5%以内,为维修现场、产线抽检及硬件调试提供了可携带的精密测量方案。全文

weixin_33910191的博客 398

rabbitmq动态创建监听

【代码】rabbitmq动态创建监听。

面朝大海,春暖花开 2909

闲人闲谈PS之一项目库存跨公司业务STO解决方案--SAP闲人的开篇

写这篇文章之前,简单说一下发这篇文章的动机。也算是在SAP这一行干了快10多年,在对一个模块了解越来越深入,越发觉自己的SAP知识浅薄。总的来说,和多数顾问一样,对于**标准功能**的应用业务场景理解还是存在或多或少的知识盲区。 因为一个偶然,看到SAP团子君的文章,分享了很多干货文章。这也是自己一直想做的时候,不管水平高低与否,分享本身就意味着提升。好了废话不多说,先在优快云上发一篇吧,困惑自己将近10年的问题的解决方案。 业务场景:在项目型企业中,通常会有这样的业务,母公司和客...

SAP闲人的博客 9269

RabbitMQ,手动ACK情况下,消费消息的时候出现异常,如何手动ACK或NACK

@RabbitListener里的errorHandler(实际是RabbitListenerErrorHandler)的方式来处理,并把spring-amqp的版本提升到2.1.7以上,消息消费出现异常之后,进行手动的ack或者nack

lzhfdxhxm的博客 6534

阿里奇门接口联调全流程详解:从沙箱自测到正式上线的保姆级攻略

本文详细解析阿里奇门接口联调的全流程,从沙箱自测到正式上线的关键步骤与避坑指南。涵盖环境配置、业务场景测试、跨团队协作、灰度发布及生产运维监控,帮助开发者高效完成API对接,确保供应链协同系统的稳定运行。特别适合首次接触奇门对接的技术负责人参考。

weixin_42535461的博客 417

微信小游戏马甲包过审策略

微信小游戏马甲包是指同一套游戏源码使用不同的名字包装上线微信小游戏平台。微信官方是不允许这样的方式上线小游戏,但是游戏运营方为了买量的需求,往往采用不同包装来,以获取更多流量。另外游戏发行以后,多家联运渠道都需要上线同一款小游戏,也存在马甲包的情况。 如果马甲包游戏源码上传时没有经过特殊处理,就会打回小游戏内容涉嫌代码包内容侵权,同时信用分-5(信用分越低,提审时间越长,一般保持在90分及以上才能保证快速审核)。微信官方回复结果如下: 目前,微信小游戏审核相比苹果商店审核还不算严格,基本上能突破代码

aqi22221的博客 4508

spring的RabbitTemplate 发送Message源码导读

spring的RabbitTemplate 发送Message源码导读

每天积累一点,一年后你会发现,自己变化很大 2万+

记录一下关于RabbitMQ消费端@RabbitListener动态配置监听队列Queue的处理过程

场景描述: 1.项目启动,通过注解获取配置文件中需要监听的队列信息 2.需要重新启动后才会生效,因为是在配置文件中 3.或许可以调整从数据库中读取或者nacos动态配置试着是不是可以不用重启,没有亲自测试,只是觉得应该也是可以 话不多说,先在配置文件中写上你想监听的queue,我们先弄个简单的单个队列示例 单个,在配置文件里面是可以正常的。那么,如果想配置多个呢。 还可以这么玩吗???哈哈哈,显然是不行的。下面来看看多个的...

easytoremeber的专栏 1万+

YOLOv8改进:注意力系列篇 |高效的通道先验卷积注意力(CPCA) | 中科院 2023.6

高效的通道先验卷积注意力(CPCA)方法,支持注意力权重在通道和空间维度上的动态分布;

会AI的学姐 2046

058、时域降噪(Temporal Denoise):运动补偿与递归滤波在视频降噪中的应用

本文深入探讨了时域降噪(Temporal Denoise)技术在视频处理中的应用。作者通过车载环视项目案例,揭示了空域降噪的局限性,提出利用帧间信息进行降噪的核心思路。文章详细解析了递归滤波和运动补偿两大关键技术,指出递归滤波简单但易产生拖影,而运动补偿通过计算运动矢量解决此问题。作者还分享了多帧降噪方案的优势与算力挑战,并总结了噪声模型匹配、运动矢量一致性等实战调优经验。最后强调时域降噪需要与空域降噪平衡,指出调试工具的重要性,提出"在保证运动区域无鬼影的前提下追求信噪比"的实践原则,体现了算法复杂度与效

zzfmlxs的博客 93

Rabbitmq使用@RabbitListener动态生成队列进行监听

private static final String QUEUE_NAME="cre_in_out."+ getIp(); @Bean public org.springframework.amqp.core.Queue queue(){ return new org.springframework.amqp.core.Queue(QUEUE_NAME,true); } @RabbitListener(bindings = @QueueBinding(.

wxz980927155的博客 6632

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

rabbitMq动态创建和监听队列

动态创建队列、监听队列

qq_42223485的博客 6811

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

rabbitmq 清理消息队列 解决 启动消费者报错

【代码】rabbitmq 清理消息队列 解决 启动消费者报错。

qq_24981615的博客 1102

Springboot RabbitMq源码解析之RabbitListener注解 (四)

RabbitListener注解源码解析

959 6496

SpringRabbit停止全部或部分消费

前言 在处理某些业务时,我们可能需要手动停止监听。让消息队列依然可以接收消息,但是先不处理消息。等业务处理完成后再开启监听,处理队列中的消息。所以这里就需要用到RabbitListenerEndpointRegistry中的start和stop方法。停止所有队列 @Autowired private RabbitListenerEndpointRegistry rabbitListenerEn...

HELLOW,文浩 1307

JMS开发指南

1.JMS消息的异步与同步接收 消息的异步接收: 异步接收是指当消息到达时,主动通知客户端,即当消息到达时转发到客户端。JMS客户端可以通过注册一个实现MessageListener接口的对象到MessageConsumer,这样,每当消息到达时,JMS Provider 会调用MessageListener中的onMessage 方法。所以需要创建一个消息监听器,然后注册一个或多个使用Mes

IT忍者神龟 828

java消息失败重发_JMS消息在JMS侦听器中重新发送异常

Javadoc为 org.springframework.jms.listener.AbstractMessageListenerContainer 表示,如果“sessionAcknowledgeMode”设置为“CLIENT_ACKNOWLEDGE”:成功侦听器执行后自动确认消息;如果抛出异常,则无法重新发送 .我猜,“在抛出异常的情况下没有重新传递”意味着,即使jms监听器中抛出异常,该消息...

weixin_42151373的博客 749

日常挖坑02——RabbitMQ消费数据失败

Caused by: org.springframework.amqp.AmqpException: No method found for class

月月大王的博客 1125
上一篇: RabbitMQ: 消息过期机制与死信队列技术解析
下一篇: RabbitMQ:高效消息处理与资源管理实践
Wang's Blog
博客等级 码龄12年 7438粉丝 2422原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值