RabbitMQ与 spring 整合

rabbitmq 与 spring 整合,熟悉 RabbitAdmin、 RabbitTemplate、SimpleMessageListenerContainer、MessageListenerAdapter 和 MessageConverter 几个类(接口)的用法。

代码示例会使用 springboot 项目进行演示

pom 依赖如下:

		<dependency>
		    <groupId>com.rabbitmq</groupId>
		    <artifactId>amqp-client</artifactId>
		    <version>5.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>
		
		<!-- json 转换 -->
		<dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-databind</artifactId>
		</dependency>

RabbitAdmin

RabbitAdmin 可以用来创建交换机、队列、绑定关系等。当然,这些交换机、队列、绑定关系也可以通过配置文件使用容器进行创建。

首先创建一个主配置类,把 RabbitAdmin 注入容器中,然后就可以直接使用 RabbitAdmin 中的方法了

@Configuration
@ComponentScan({"com.xiao.*"})
public class RabbitmqConfig {

	@Bean
	public ConnectionFactory connectionFactory() {
		CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
		cachingConnectionFactory.setAddresses("192.168.0.125:5672");
		cachingConnectionFactory.setUsername("guest");
		cachingConnectionFactory.setPassword("guest");
		cachingConnectionFactory.setVirtualHost("/");
		return cachingConnectionFactory;
	}
	
	@Bean
	public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) { 
		// 该方法的参数必须与上一个bean的方法名相同,否则在容器中无法找到该对象
		RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
		// 必须设置为 true,否则容器不会加载 RabbitAdmin 类
		rabbitAdmin.setAutoStartup(true);
		return rabbitAdmin;
	}
}

在测试类中使用 RabbitAdmin 的功能

@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqTest {

	@Autowired
	RabbitAdmin rabbitAdmin;
	
	@Test
	public void testRabbitAdmin() {
		// 第一种绑定方式 
		rabbitAdmin.declareExchange(new TopicExchange("admin.topic.exchange", false, false));
		
		rabbitAdmin.declareQueue(new Queue("admin.queue", false, false, false));
		
		//rabbitAdmin.declareBinding(new Binding(destination, destinationType, exchange, routingKey, arguments));
		rabbitAdmin.declareBinding(new Binding("admin.queue", Binding.DestinationType.QUEUE, "admin.topic.exchange", "test.#", null));
		
/*		
		// 第二种绑定方式 
		rabbitAdmin.declareBinding(BindingBuilder
				.bind(new Queue("admin.queue",false,false,false))
				.to(new TopicExchange("admin.topic.exchange", false, false))
				.with("builder.#"));
*/		
		// 清空队列中的消息数据
		//rabbitAdmin.purgeQueue(queueName, noWait);
		//rabbitAdmin.purgeQueue("admin.queue", false);
	}
}

我运行了几次,发现第一种方法可以直接创建交换机和队列,并建立绑定关系,但是第二种方法只能使用已经存在的交换机和队列,如果写不存在的交换机和队列名会报错。

在配置类中通过容器创建交换机、队列和绑定关系,只要在配置类中使用 @Bean 注解即可

	@Bean
	public TopicExchange exchange1() {
		return new TopicExchange("topicExchange001", false, false);
	}
	
	@Bean
	public Queue queue1() {
		return new Queue("queue001", false);
	}
	
	@Bean
	public Binding binding1() {
		return BindingBuilder.bind(queue1()).to(exchange1()).with("bean.#");
	}

执行上面的测试方法即可看到 topicExchange001 和 queue001 及绑定关系已经创建了

RabbitTemplate

  • RabbitTemplate 与 springAMQP 整合是发送消息的关键类,可以设置消息的属性 MessageProperties
  • 该类提供了丰富的发送消息方法,需注入到 spring 容器进行使用

首先需要在配置类中注入 RabbitTemplate 实例,同 RabbitAdmin

	@Bean
	public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
		RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
		return rabbitTemplate;
	}

测试用例

	@Autowired
	RabbitTemplate rabbitTemplate;
	
	@Test
	public void testRabbitTemplage() {
		MessageProperties messageProperties = new MessageProperties();
		messageProperties.getHeaders().put("hid", "head id");
		messageProperties.getHeaders().put("hfun", "head 功能说明");
		Message message = new Message("rabbitTemplage.convertAndSend 发送消息。。。 ".getBytes(), messageProperties);
		
		//rabbitTemplate.convertAndSend(exchange, routingKey, message, messagePostProcessor);
		rabbitTemplate.convertAndSend("topicExchange001", "bean.123", message, new MessagePostProcessor() {
			@Override
			public Message postProcessMessage(Message message) throws AmqpException {
				System.err.println("------------- 消息后置处理 -------------");
				message.getMessageProperties().getHeaders().put("hfun", "head 后置处理后的功能说明  ");
				message.getMessageProperties().getHeaders().put("htype", "head 后置处理新增的类型  ");
				return message;
			}
		});
	}
	
	@Test
	public void testRabbitTemplage2() {
		MessageProperties messageProperties = new MessageProperties();
		messageProperties.setContentType("text/plain");
		Message message = new Message("rabbitTemplage.send 发送 1 条消息。。。 ".getBytes(), messageProperties);
		
		//rabbitTemplate.send(exchange, routingKey, message);
		rabbitTemplate.send("topicExchange001", "bean.aaa", message);
		//rabbitTemplate.convertAndSend(exchange, routingKey, object);
		rabbitTemplate.convertAndSend("admin.topic.exchange", "test.111", "rabbitTemplate.convertAndSend 发送消息对象 ");
	}

运行 testRabbitTemplage 测试方法后,去控制台中查看消息
在这里插入图片描述testRabbitTemplage2 测试方法就是看看发送消息的其他方式
在这里插入图片描述

SimpleMessageListenerContainer

  • 可以动态设置消费者配置项,如最大消费者、ack模式、消费者标签策略等(个人理解,类比成连接池)
  • 可以监听(多个)队列、接收消息
	@Bean
	public SimpleMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory) {
		SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory);
		//listenerContainer.setQueues(queue1()); // 设置要监听的队列
		listenerContainer.setQueueNames("admin.queue","queue001"); // 会覆盖上面一行设置的监听队列
		listenerContainer.setConcurrentConsumers(1); // 限制当前消费者数量
		listenerContainer.setMaxConcurrentConsumers(3); // 限制最大消费者数量
		listenerContainer.setDefaultRequeueRejected(false);  // 不要重回队列
		listenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);  // 自动签收
		listenerContainer.setConsumerTagStrategy(new ConsumerTagStrategy() {  // 自定义消费者标签策略
			@Override
			public String createConsumerTag(String queue) {
				return queue + "_"+ UUID.randomUUID().toString();
			}
		});
	
		listenerContainer.setMessageListener(new ChannelAwareMessageListener() { // 消息监听
			@Override
			public void onMessage(Message message, Channel channel) throws Exception {
				String msg = new String(message.getBody());
				System.err.println("---------MessageListener 接收消息: " + msg);
			}
		});
		return listenerContainer;
	}

由于执行了上面两个发送消息的测试用例,现在"admin.queue","queue001"这两个队列是一共有 3 条消息,运行主入口类,可以看到 3 条消息都已被监听,且自动签收。
在这里插入图片描述

MessageListenerAdapter

  • 不采用其提供的消息监听,可以自定义适配器,适配自己编写的接收消息处理类。默认方法 handleMessage(byte[] messagebody)
  • 可以指定为其他方法,
  • 可以通过转换器改变参数类型
  • 可以给队列绑定方法

MessageConverter

  • 该类提供多种转换器
  • 可自定义常用转换器,一般都会实现这个接口

由于适配器和转换器都是添加到监听容器的,代码示例就直接放一起了

首先创建一个 适配器 类

public class MessageAdapter {

	public void handleMessage(byte[] msgBody) {
		System.out.println("默认方法:消息内容——>" + new String(msgBody));
	}
	
	public void modifiedMessageMethod(byte[] msgBody) {
		System.out.println("字节数组方法:消息内容——>" + new String(msgBody));
	}
	
	public void modifiedMessageMethod(String msgBody) {
		System.out.println("字符串方法:消息内容——>" + msgBody);
	}
	
	public void queueMethod(String msgBody) {
		System.out.println("队列名匹配方法:消息内容——>" + msgBody);
	}
	
	public void queueMethod2(String msgBody) {
		System.out.println("队列名匹配方法2:消息内容——>" + msgBody);
	}
	
	public void modifiedMessageMethod(Map msgBody) {
		System.out.println("map转换方法:消息内容——>" + msgBody);
	}
	
	public void modifiedMessageMethod(Student stu) {
		System.out.println("Student 对象:消息内容——> sid==" + stu.getSid() + ", sname==" + stu.getSname());
	}
	
	public void modifiedMessageMethod(Address addr) {
		System.out.println("Address 对象:消息内容——> province==" + addr.getProvince() + ", city==" + addr.getCity());
	}
	
	public void modifiedMessageMethod(File file) {
		System.out.println("文件对象:消息内容——>" + file.getName());
	}
	
}

另外用到了两个实体类,代码就不贴了。(注意:实体类须实现序列化,并且要有无参构造方法
下面是若干自定义转换器类

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;

public class TextMessageConverter implements MessageConverter {

	@Override
	public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
		
		return new Message(object.toString().getBytes(), messageProperties);
	}

	@Override
	public Object fromMessage(Message message) throws MessageConversionException {
		String contentType = message.getMessageProperties().getContentType();
		if(contentType != null && contentType.contains("text")) {
			return new String(message.getBody());
		}
		
		return message.getBody();
	}

}
public class ImageMessageConverter implements MessageConverter {

	@Override
	public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
		throw new MessageConversionException("converter error !");
	}

	@Override
	public Object fromMessage(Message message) throws MessageConversionException {
		System.err.println("--------------Image Converter--------------");
		Object extName = message.getMessageProperties().getHeaders().get("extName");
		String imgExtName = extName == null? "jpg": extName.toString();
		
		byte[] msgBody = message.getBody();
		String fileName = UUID.randomUUID().toString();
		String path = "e:/testFile/" + fileName + "." + imgExtName;
		File file = new File(path);
		try {
			Files.copy(new ByteArrayInputStream(msgBody), file.toPath());
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return file;
	}

}
public class PDFMessageConverter implements MessageConverter {

	@Override
	public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
		throw new MessageConversionException("converter error !");
	}

	@Override
	public Object fromMessage(Message message) throws MessageConversionException {
		System.err.println("--------------PDF Converter--------------");
		
		byte[] msgBody = message.getBody();
		String fileName = UUID.randomUUID().toString();
		String path = "e:/testFile/" + fileName + ".pdf";
		File file = new File(path);
		try {
			Files.copy(new ByteArrayInputStream(msgBody), file.toPath());
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return file;
	}

}

主配置类中把适配器和转换器添加到监听器中

	@Bean
	public SimpleMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory) {
		SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory);
		//listenerContainer.setQueues(queue1()); // 设置要监听的队列
		listenerContainer.setQueueNames("admin.queue","queue001"); // 会覆盖上面一行设置的监听队列
		listenerContainer.setConcurrentConsumers(1); // 限制当前消费者数量
		listenerContainer.setMaxConcurrentConsumers(3); // 限制最大消费者数量
		listenerContainer.setDefaultRequeueRejected(false);  // 不要重回队列
		listenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);  // 自动签收
		listenerContainer.setConsumerTagStrategy(new ConsumerTagStrategy() {  // 自定义消费者标签策略
			@Override
			public String createConsumerTag(String queue) {
				return queue + "_"+ UUID.randomUUID().toString();
			}
		});
/*		
		listenerContainer.setMessageListener(new ChannelAwareMessageListener() { // 消息监听
			@Override
			public void onMessage(Message message, Channel channel) throws Exception {
				String msg = new String(message.getBody());
				System.err.println("---------MessageListener 接收消息: " + msg);
			}
		});
*/		
		/**
		 * 1. 适配方式:有字节默认的方法名  (handleMessage)
		 * 	可指定为自定义的方法名 (modifiedMessageMethod)
		 * 	可添加转换器,将字节数组转为字符串
		 */
/*		MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
		//使用默认的方法则注释下面两行即可
		messageAdapter.setDefaultListenerMethod("modifiedMessageMethod");
		messageAdapter.setMessageConverter(new TextMessageConverter());
		listenerContainer.setMessageListener(messageAdapter);
*/		
		
		/**
		 * 2. 适配方式:将队列名称与方法名匹配
		 * 	多个队列可以匹配同一个方法名
		 */
/*		MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
		Map<String, String> queueOrTagToMethodName = new HashMap<String, String>();
		queueOrTagToMethodName.put("queue001", "queueMethod");
		queueOrTagToMethodName.put("admin.queue", "queueMethod");
		messageAdapter.setQueueOrTagToMethodName(queueOrTagToMethodName);
		messageAdapter.setMessageConverter(new TextMessageConverter());
		listenerContainer.setMessageListener(messageAdapter);
*/		
		
		// json 格式转换
/*		MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
		messageAdapter.setDefaultListenerMethod("modifiedMessageMethod");
		
		Jackson2JsonMessageConverter jsonMessageConverter = new Jackson2JsonMessageConverter();
		messageAdapter.setMessageConverter(jsonMessageConverter);
		
		listenerContainer.setMessageListener(messageAdapter);
*/		
		
		// java 对象转换
/*		MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
		messageAdapter.setDefaultListenerMethod("modifiedMessageMethod");
		
		Jackson2JsonMessageConverter jsonMessageConverter = new Jackson2JsonMessageConverter();
		DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
		// 在SpringBoot升级后,之前的系统内使用实体传输受到了限制,如果使用SpringBoot默认的序列化方式不会出现信任package的问题,
		// 之所以出现这个问题是因为项目使用fastjson方式进行类的序列化已经反序列化,在之前SpringBoot 1.5.10版本的时候 RabbitMQ依赖内
		// 的DefaultClassMapper类在构造函数内配置*,表示信任项目内的所有package,在SpringBoot 2.0.0版本时,
		// DefaultClassMapper类源码构造函数进行了修改,不再信任全部package而是仅仅信任java.util、java.lang。
		
		//SpringBoot2.0新特性 - RabbitMQ信任package设置
		javaTypeMapper.addTrustedPackages("com.xiao.entity");
		
		jsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
		messageAdapter.setMessageConverter(jsonMessageConverter);
		
		listenerContainer.setMessageListener(messageAdapter);
*/		
		
		// java 对象多映射转换
		MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
		messageAdapter.setDefaultListenerMethod("modifiedMessageMethod");
		
		Jackson2JsonMessageConverter jsonMessageConverter = new Jackson2JsonMessageConverter();
		DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
		
		Map<String, Class<?>> javaObjMap = new HashMap<String, Class<?>>();
		javaObjMap.put("student", Student.class);
		javaObjMap.put("address", Address.class);
		javaTypeMapper.setIdClassMapping(javaObjMap);
		
		javaTypeMapper.addTrustedPackages("com.xiao.entity");
		jsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
		messageAdapter.setMessageConverter(jsonMessageConverter);
		
		listenerContainer.setMessageListener(messageAdapter);
		
/*		
		// 全局转换器
		MessageListenerAdapter messageAdapter = new MessageListenerAdapter(new MessageAdapter());
		messageAdapter.setDefaultListenerMethod("modifiedMessageMethod");
		
		ContentTypeDelegatingMessageConverter converter = new ContentTypeDelegatingMessageConverter();
		
		TextMessageConverter messageConverter = new TextMessageConverter();
		converter.addDelegate("text", messageConverter);
		converter.addDelegate("html/text", messageConverter);
		converter.addDelegate("xml/text", messageConverter);
		converter.addDelegate("text/plain", messageConverter);
		
		Jackson2JsonMessageConverter jsonMessageConverter = new Jackson2JsonMessageConverter();
		converter.addDelegate("json", jsonMessageConverter);
		converter.addDelegate("application/json", jsonMessageConverter);
		
		ImageMessageConverter imageMessageConverter = new ImageMessageConverter();
		converter.addDelegate("image", imageMessageConverter);
		converter.addDelegate("image/jpg", imageMessageConverter);
		
		PDFMessageConverter pdfMessageConverter = new PDFMessageConverter();
		converter.addDelegate("application/pdf", pdfMessageConverter);
		
		messageAdapter.setMessageConverter(converter);
		listenerContainer.setMessageListener(messageAdapter);
*/		
		return listenerContainer;
	}

测试用例:

	@Test
	public void testMessage4Text() {
		MessageProperties messageProperties = new MessageProperties();
		messageProperties.setContentType("text/plain");
		Message message = new Message("一条 text 消息。。。 ".getBytes(), messageProperties);
		
		rabbitTemplate.send("topicExchange001", "bean.aaa", message);
	}
	
	@Test
	public void testSendJsonMessage() throws Exception {
		Student stu = new Student("001", "张三");
		ObjectMapper objectMapper = new ObjectMapper();
		String stuString = objectMapper.writeValueAsString(stu);
		System.err.println("stu 2 json :" + stuString);
		
		MessageProperties messageProperties = new MessageProperties();
		// 务必设置 ContentType 为 json 类型
		messageProperties.setContentType("application/json");
		Message message = new Message(stuString.getBytes(), messageProperties);
		
		rabbitTemplate.send("topicExchange001", "bean.aaa", message);
		
	}
	
	@Test
	public void testSendJavaMessage() throws Exception {
		Student stu = new Student("002", "李四");
		ObjectMapper objectMapper = new ObjectMapper();
		String json = objectMapper.writeValueAsString(stu);
		System.err.println("stu 2 json :" + json);
		
		MessageProperties messageProperties = new MessageProperties();
		// 务必设置 ContentType 为 json 类型
		messageProperties.setContentType("application/json");
		// 键为固定值,两边分别是两个下划线;值是 java 对象的全路径
		messageProperties.getHeaders().put("__TypeId__", "com.xiao.entity.Student");
		Message message = new Message(json.getBytes(), messageProperties);
		
		rabbitTemplate.send("topicExchange001", "bean.aaa", message);
		
	}
	
	@Test
	public void testSendMappingJavaMessage() throws Exception {
		ObjectMapper objectMapper = new ObjectMapper();
		
		Student stu = new Student("003", "王五");
		String stuJson = objectMapper.writeValueAsString(stu);
		System.err.println("stu 2 json :" + stuJson);
		
		MessageProperties messageProperties = new MessageProperties();
		// 务必设置 ContentType 为 json 类型
		messageProperties.setContentType("application/json");
		messageProperties.getHeaders().put("__TypeId__", "student");
		Message message = new Message(stuJson.getBytes(), messageProperties);
		rabbitTemplate.send("topicExchange001", "bean.aaa", message);
		
		Address addr = new Address("北京", "丰台");
		String addrJson = objectMapper.writeValueAsString(addr);
		System.err.println("addr 2 json :" + addrJson);
		
		MessageProperties messageProperties2 = new MessageProperties();
		// 务必设置 ContentType 为 json 类型
		messageProperties2.setContentType("application/json");
		messageProperties2.getHeaders().put("__TypeId__", "address");
		Message message2 = new Message(addrJson.getBytes(), messageProperties2);
		rabbitTemplate.send("topicExchange001", "bean.aaa", message2);
		
	}
	
	@Test
	public void testSendExtMessage() throws Exception {
		byte[] msgBody = Files.readAllBytes(Paths.get("e:/smallicon/money.jpg"));
		MessageProperties messageProperties = new MessageProperties();
		messageProperties.setContentType("image/jpg");
		messageProperties.getHeaders().put("extName", "jpg");
		Message message = new Message(msgBody, messageProperties);
		rabbitTemplate.send("topicExchange001", "bean.aaa", message);
	/*	
		byte[] msgBody = Files.readAllBytes(Paths.get("e:/test.pdf"));
		MessageProperties messageProperties = new MessageProperties();
		messageProperties.setContentType("application/pdf");
		Message message = new Message(msgBody, messageProperties);
		rabbitTemplate.send("topicExchange001", "bean.aaa", message);
	*/	
	}

下面是一些效果截图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring 框架提供了对 RabbitMQ 的支持,可以方便地将 RabbitMQ 集成到 Spring 项目中。 下面是整合 RabbitMQ 的步骤: 1. 添加 RabbitMQ 的依赖 在 pom.xml 文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>2.3.1.RELEASE</version> </dependency> ``` 2. 配置 RabbitMQ 连接信息 在 application.properties(或 application.yml)文件中添加 RabbitMQ 连接信息: ```properties spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest ``` 3. 创建 RabbitMQ 队列 使用 `RabbitAdmin` 对象创建队列: ```java @Configuration public class RabbitMQConfig { @Bean public Queue myQueue() { return new Queue("myQueue"); } @Bean public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) { return new RabbitAdmin(connectionFactory); } } ``` 4. 发送消息 使用 `RabbitTemplate` 对象发送消息: ```java @Autowired private RabbitTemplate rabbitTemplate; public void sendMessage(String message) { rabbitTemplate.convertAndSend("myQueue", message); } ``` 5. 接收消息 使用 `@RabbitListener` 注解指定要监听的队列,并使用 `Message` 对象接收消息: ```java @RabbitListener(queues = "myQueue") public void receiveMessage(Message message) { String content = new String(message.getBody()); System.out.println("Received message: " + content); } ``` 以上就是使用 Spring 框架整合 RabbitMQ 的步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值