RabbitMQ从安装开始...(持续更新中)

rabbitMQ安装需要的文件,链接:https://pan.baidu.com/s/1so8ta3CP1zI6JNO4zt2sNw  提取码:vi47

一.安装rabbitMQ

1.安装erlang环境

rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm

2.安装socat

若不安装socat,直接安装rabbit-server,则会出现

安装socat

rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps

这里的安装要加上  --force --nodeps,不加上则会出现

3.安装rabbitmq-server

rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm

4.修改rabbit.app配置

通过rpm安装的rabbitMQ,这个配置默认在 /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/文件件下

编辑rabbit.app

vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app

进入rabbit.app,通过 /loopback_users ,回车找到配置项,修改为

{loopback_users, []}

测试阶段,所有都能访问。在配置文件中也有默认用户名和默认密码

{default_user, <<"guest">>},
{default_pass, <<"guest">>},

启动

rabbitmq-server start &

5.安装管理页面的工具

rabbitmq-plugins enable rabbitmq_management

通过页面显示为,默认账号密码为  guest   guest

 

==============================2020年6月12日19:36:20更新===============================

通过账号密码进入rabbitMQ的管理页面之后,这里说一下Overview页面

在页面上的其他选项,就是rabbitMQ的其他东西,有connection(连接数)channels(通道) exchanges(交换机)queues(队列),这些东西,在下面会写出。

二.走进rabbitMQ

1.AMQP协议

我理解的AMQP协议只是一种协议规范,任何遵守该规范的事务,都可以进行该规范实现的功能。类似于MQTT协议,也是一种规范。我这里就理解这么多,更多更深层关于AMQP协议还没有了解过。rabbitMQ是遵守AMQP协议规范的,所以这里先说一下AMQP协议的东西。

2.AMQP协议模型

       该模型图借鉴于慕课网讲rabbitMQ课程里面的图。讲讲我对AMQP协议模型的理解。 Publisher application相当于生产者,生产者生产的消息会发送给broker,也可以叫server。我们可以看到右边是层级的关系。消息先到server,再到Virtual host(虚拟地址),用于进行逻辑的隔离,最上层的消息路由。一个Virtual host里面可以有若干个exchange和queue,但是不能有相同名称的exchange和queue,最后到达exchange(交换机)。Consumer application相当于消费者,他会读取Message Queue(队列)中的数据。但是现在数据在exchange(交换机)中,所以exchange(交换机)和queue(队列)需要建立绑定的关系,这样消费者就能通过队列拿到消息了。还有一个核心的名词,connection(链接),每次都要创建链接,channel(通道),message(消息体),routing key(路由key)。

3.rabbitMQ架构

P:生产者

X:交换机

红色的格子:队列

C:消费者

和上面的AMQP协议模型差不太多。

4.rabbitMQ消息流转

生产者生产消息Message放入到exchange,exchange和queue建立绑定,消息在到达queue,消费者相当于定于这个queue,当消息到达指定的queue之后,消费者消费掉消息。

5.rabbitMQ消息的可靠性投递

可靠性投递1:

对于可靠性投递,是对于生产端来讲的。如何100%的投递到MQ broker,这里说一下。

step1:生产者sender生产一条消息,分别入库到BIZ DB(业务库)和 MSG DB(日志信息库)。这里的业务库,就是正常的业务逻辑,比如订单的一些信息。日志信息库就是这条消息的状态,比如 status 0 1 2分别代表 0 未消费 1 消费成功 2 消息未达,消费失败。

step2:入库之后,再把消息发送到MQ Broker。

step3:MQ Broker发送确认消息,生产者中有个一个确认接收的监听器,会一直监听。

step4:当生产者受到确认消息后,同事向MSG DB(日志信息库)更新这条日志消息,状态改为status 1,消费成功。

以上步骤是全部都顺利的情况,当消息没有到达MQ Broker或者生产者没有接收到 MQ Broker的确认消息时。会有以下的步骤

step5:会有一个定时器,不断去查询 MSG DB(日志信息库)status=0状态的消息。

step6:查询到status=0的记录,并且在设置的时间内还有变为status=1,则认为消息没有到达MQ Broker或者producer未确认,则对这条消息进行重新发送。

step7:MSG DB(日志信息库)中的一条记录的Retry Count(重试次数)大于3时,则认为这条消息未达,消费失败,status=2。可以进行人工补偿。

可靠性投递2:

第一种可靠性投递在核心业务中,会有两次的入库操作,一次业务入库(必须),一次消息入库。而在核心业务中,有这样一次操作,是不太合适的,会消耗核心业务的时间,从而影响业务的响应时间。而这种可靠性投递,可以免去在核心业务的消息入库的操作。我们了解一下。

step1:发送消息到MQ Broker,同时业务数据也要落库 BIZ DB,

step2:这一步是和step1是同时进行的,这个是发一个延时消息(3分钟或5分钟)。

step3:Downstream service(下游服务)去监听来自生产者的消息。

step4:当Downstream service(下游服务)消费完消息,同时也向指定的队列中发送一个确认接收的消息A。

step5:有一个Callback service(回调服务)监听下游服务发送的确认接收消息A,在回调服务中,监听到来自于下游服务的确认消息,则进行消息落库。

step6:Callback service(回调服务)会监听step2发送的延时消息,受到消息后,去MSG DB(消息库)查询,若查到了,则消息顺利完成投递。若查询不到,则在回调服务中在放松一个RPC请求,到Upstream service(上游服务),在进行消息投递。

这样,在核心业务中,就只有了一次必须的业务落库,消息落库就会在非核心的回调服务中进行,这样的效率就会好点。

6.消息机制

rabbitMQ中有确认消息,return消息,消费端ack的限流,重回队列,死信队列,下面介绍一下。

maven依赖

<dependency>
	<groupId>com.rabbitmq</groupId>
	<artifactId>amqp-client</artifactId>
	<version>3.6.5</version>
</dependency>

 

6.1 确认消息(系统默认消费者)

确认消息就是当生产者发送一条消息时,设置一个监听器,当消费者确认接收到消息时,就会返回ack,否则返回 nack

生产者:

package com.an.rabbitmq.confirm;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 * 确认消息流程
 * @author Administrator
 */
public class Producer {

	public static void main(String[] args) throws IOException, TimeoutException {

		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.1.101");
		factory.setPort(5672);
		factory.setVirtualHost("/");
		
		Connection connection = factory.newConnection();
		// 创建一个新的通道
		Channel channel = connection.createChannel();
		// 指定消息的投递模式:消息的确认模式
		channel.confirmSelect();
		
		String exchangeName = "test_confirm_exchange";
		String routingKey = "confirm.save";

		// 发送一条消息
		String msg = "Hello rabbitMQ send confirm message!";
		channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
		
		// 添加一个确认监听器
		channel.addConfirmListener(new ConfirmListener() {
			
			@Override
			public void handleNack(long deliveryTag, boolean multiple) throws IOException {
				// 没有接收到确认消息
				System.out.println("No ack!");
			}
			
			@Override
			public void handleAck(long deliveryTag, boolean multiple) throws IOException {
				// 接收到了确认消息
				System.out.println("ack!");
			}
		});
		
	}
}

消费者

package com.an.rabbitmq.confirm;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.QueueingConsumer.Delivery;

/**
 * 确认消息流程
 * @author Administrator
 */
public class Consumer {
	public static void main(String[] args) throws IOException, TimeoutException ,Exception{

		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.1.101");
		factory.setPort(5672);
		factory.setVirtualHost("/");
		
		Connection connection = factory.newConnection();
		
		// 创建一个新的通道
		Channel channel = connection.createChannel();
		
		// 指定消息的投递模式:消息的确认模式
		channel.confirmSelect();
		
		String exchangeName = "test_confirm_exchange";
		String queueName = "test_confirm_queue";
		String routingKey = "confirm.#";
		
		channel.exchangeDeclare(exchangeName, "topic",true);
		channel.queueDeclare(queueName, true, false, false, null);
		channel.queueBind(queueName, exchangeName, routingKey);
		
		QueueingConsumer consumer = new QueueingConsumer(channel);
		
		channel.basicConsume(queueName,true, consumer);
		
		while(true) {
			Delivery delivery = consumer.nextDelivery();
			String msg = new String(delivery.getBody());
			System.out.println("消费端:"+msg);
		}

	}
}

6.2 返回消息

在生产者中设置返回消息监听器,当生产者的消息没有被消费者接收到时,消息返回到生产者的监听器中。

生产者中,有一个参数mandatory,设置为true时,才会将消息返回到生产者的监听器中。若设置为false,那么broker端自动删除该消息。

生产者:

package com.an.rabbitmq.returnlistener;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ReturnListener;
import com.rabbitmq.client.AMQP.BasicProperties;

/**
 * 返回的消息。当消息不可达,切在消费者中的mandatory参数设置为true时,则将消息返回到生产者
 * @author Administrator
 */
public class Producer {
	
	public static void main(String[] args) throws Exception {
		
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.1.101");
		factory.setPort(5672);
		factory.setVirtualHost("/");
		
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		
		String exchangeName = "test_biz_exchange";
		String routingKey = "biz.save";
		
		String routingKeyError = "abc.save";
		
		String msg = "Hello rabbitMQ!";
		
		// 设置监听器,当没有消费者接收到消息时,消息返回到生产者的监听器中
		channel.addReturnListener(new ReturnListener() {
			@Override
			public void handleReturn(int replyCode, String replyText, String exchange,
					String routingKey, BasicProperties properties, byte[] body)
					throws IOException {
				System.err.println("replyCode:"+replyCode);
				System.err.println("replyText:"+replyText);
				System.err.println("exchange:"+exchange);
				System.err.println("routingKey:"+routingKey);
				System.err.println("properties:"+properties);
				System.err.println("body:"+new String(body));
			}
		});
		
		// 第三个参数 mandatory 设置true时,当消息不可达时,会将消息返回给生产者中的returnListener
		channel.basicPublish(exchangeName, routingKeyError, true, null, msg.getBytes());
		
		
		
		
		
	}

}

消费者:

package com.an.rabbitmq.returnlistener;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.QueueingConsumer.Delivery;

public class Consumer {
	
	public static void main(String[] args) throws Exception {
		
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.1.101");
		factory.setPort(5672);
		factory.setVirtualHost("/");
		
		String queueName = "test_return_queue";
		String exchangeName = "test_biz_exchange";
		String routingKey = "biz.#";
		
		
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		
		channel.exchangeDeclare(exchangeName, "topic",true,false,null);
		channel.queueDeclare(queueName, true, false, false, null);
		channel.queueBind(queueName, exchangeName, routingKey);
		
		QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
		
		 
		// param1: queue(队列名称),param2: autoAck(自动确认接收), param3: callback(消费者)
		channel.basicConsume(queueName, true, queueingConsumer);
		
		while(true) {
			Delivery delivery = queueingConsumer.nextDelivery();
			String msg = new String(delivery.getBody());
			System.out.println("消费者:"+msg);
		}
		
	}

}

6.3 消费端限流(自定义消费者)

若rabbitMQ服务器有上万条数据没有处理,若果我们一打开消费者,大量的数据会涌向消费者,可能消费者服务器无法处理这么多的数据,就会出现一些问题。rabbitMQ提供了一种qos(服务质量保证)功能,在非自动确认消息的前提下,如果一定数目的消息未被确认,不进行消费新的消息。

生产者:

package com.an.rabbitmq.limit;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {
	
	public static void main(String[] args) throws Exception {
		
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.1.101");
		factory.setPort(5672);
		factory.setVirtualHost("/");
		Connection connection = factory.newConnection();
		
		String exchangeName = "test_my_exchange";
		String routingKey = "my.save";
		
		Channel channel = connection.createChannel();
		
		for(int i=0;i<5;i++) {
			channel.basicPublish(exchangeName, routingKey, null, ("Hello rabbitMQ "+i).getBytes());
		}
		
	}
}

消费者:

package com.an.rabbitmq.limit;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 * 消费端限流
 * @author Administrator
 *
 */
public class Customer {
	
	public static void main(String[] args) throws Exception {
		
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.1.101");
		factory.setPort(5672);
		factory.setVirtualHost("/");
		
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		String queueName = "my_queue";
		String exchangeName = "test_my_exchange";
		String routingKey = "my.#";
		
		channel.exchangeDeclare(exchangeName, "topic");
		channel.queueDeclare(queueName, true, false, false, null);
		channel.queueBind(queueName, exchangeName, routingKey);
		
		// 质量保证功能,如果一定数量的消息未被确认前,不进行消费新的消息
		// 第二个参数prefetchCount:  如果有 N个消息没有ack,则该消费者会阻塞,知道ack。在no_ack时才生效,要手工ack
		
		channel.basicQos(0, 1, false);
		// 限流方式,autoAck设置为false
		channel.basicConsume(queueName,false, new MyCustomer(channel));
		
	}
}

自定义消费者

package com.an.rabbitmq.limit;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

/**
 * 自定义消费者
 * @author Administrator
 *
 */
public class MyCustomer extends DefaultConsumer{

	private Channel channel;
	
	public MyCustomer(Channel channel) {
		super(channel);
		this.channel = channel;
	}
	
	@Override
	public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
			throws IOException {
		System.err.println("-----------consume message----------");
		System.err.println("consumerTag: " + consumerTag);
		System.err.println("envelope: " + envelope);
		System.err.println("properties: " + properties);
		System.err.println("body: " + new String(body));
		
		// 消费端手动确认,确认后才进行下一条消息的确认
		channel.basicAck(envelope.getDeliveryTag(), false);
		
	}
	
}

6.4 消息确认和重返队列

当生产者发送消息时,消费者没有接收到,会再次放松到消费者中,直到消息确认成功。

生产者:

package com.an.rabbitmq.ack;

import java.util.HashMap;
import java.util.Map;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 * 消息确认和重返队列
 * @author Administrator
 *
 */
public class Producer {
	
	public static void main(String[] args) throws Exception {
		
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.1.101");
		factory.setPort(5672);
		factory.setVirtualHost("/");
		Connection connection = factory.newConnection();
		
		String exchangeName = "test_ack_exchange";
		String routingKey = "ack.save";
		
		Channel channel = connection.createChannel();
		
		for(int i=0;i<5;i++) {
			Map<String, Object> headers = new HashMap<String, Object>();
			headers.put("num", i);
			AMQP.BasicProperties prop = new AMQP.BasicProperties.Builder()
									.deliveryMode(2)
									.contentEncoding("UTF-8")
									.headers(headers)
									.build();
			// mandatory 第三个参数 mandatory 设置true时,当消息不可达时,会将消息返回给生产者中的returnListener
			channel.basicPublish(exchangeName, routingKey,true, prop, ("Hello rabbitMQ "+i).getBytes());
		}
		
	}
}

消费者:

package com.an.rabbitmq.ack;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 * 消息确认和重返队列
 * @author Administrator
 */
public class Customer {
	
	public static void main(String[] args) throws Exception {
		
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.1.101");
		factory.setPort(5672);
		factory.setVirtualHost("/");
		
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		String queueName = "ack_queue";
		String exchangeName = "test_ack_exchange";
		String routingKey = "ack.#";
		
		channel.exchangeDeclare(exchangeName, "topic");
		channel.queueDeclare(queueName, true, false, false, null);
		channel.queueBind(queueName, exchangeName, routingKey);
		
		// 质量保证功能,如果一定数量的消息未被确认前,不进行消费新的消息
		// 第二个参数prefetchCount:  如果有 N个消息没有ack,则该消费者会阻塞,知道ack。在no_ack时才生效,要手工ack
		//	channel.basicQos(0, 1, false);
		// 限流方式,autoAck设置为false
		channel.basicConsume(queueName,false, new MyCustomer(channel));
		
	}
}

自定义消费者:

package com.an.rabbitmq.ack;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

/**
 * 自定义消费者 重回队列
 * @author Administrator
 */
public class MyCustomer extends DefaultConsumer{

	private Channel channel;
	
	public MyCustomer(Channel channel) {
		super(channel);
		this.channel = channel;
	}
	
	@Override
	public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
			throws IOException {
		System.err.println("-----------consume message----------");
		System.err.println("body: " + new String(body));
		
		try {
			Thread.sleep(1000);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		Integer num = (Integer)properties.getHeaders().get("num");
		
		if(num == 0) {
			// 第三个参数requeue设置为true,当消息未被确认,重返队列
			channel.basicNack(envelope.getDeliveryTag(), false, true);
		}else {
			channel.basicAck(envelope.getDeliveryTag(), false);
		}
		
	}
	
}

6.4 死信队列

生产者:

package com.an.rabbitmq.dlx;


import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;


/**
 * 死信队列
 * @author Administrator
 *
 */
public class Producer {
	
	public static void main(String[] args) throws Exception {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.1.101");
		factory.setPort(5672);
		factory.setVirtualHost("/");
		Connection connection = factory.newConnection();
		
		String exchangeName = "test_dlx_exchange";
		String routingKey = "dlx.save";
		Channel channel = connection.createChannel();
		
		for(int i=0;i<1;i++) {
			AMQP.BasicProperties prop = new AMQP.BasicProperties.Builder()
										.deliveryMode(2)
										.contentEncoding("UTF-8")
										.expiration("10000") // 设置队列消息10秒后过期
										.build();
			channel.basicPublish(exchangeName, routingKey, true,prop, ("Hello rabbitMQ "+i).getBytes());
		}
	}
}

消费者:

package com.an.rabbitmq.dlx;

import java.util.HashMap;
import java.util.Map;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Customer {
	
	public static void main(String[] args) throws Exception {
		
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.1.101");
		factory.setPort(5672);
		factory.setVirtualHost("/");
		
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		String queueName = "test_dlx_queue";
		String exchangeName = "test_dlx_exchange";
		String routingKey = "dlx.#";
		
		channel.exchangeDeclare(exchangeName, "topic");
		Map<String, Object> arguement = new HashMap<String, Object>();
		arguement.put("x-dead-letter-exchange", "dlx.exchange");
		
		channel.queueDeclare(queueName, true, false, false, arguement);
		channel.queueBind(queueName, exchangeName, routingKey);
		
		
		// 生命死信队列
		channel.exchangeDeclare("dlx.exchange", "topic",true,false,null);
		channel.queueDeclare("dlx.queue", true, false, false, null);
		channel.queueBind("dlx.queue", "dlx.exchange", "#");
		
		channel.basicConsume(queueName,true, new MyCustomer(channel));
		
	}
}

自定义消费者:

package com.an.rabbitmq.dlx;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

/**
 * 自定义消费者 死信队列
 * @author Administrator
 *
 */
public class MyCustomer extends DefaultConsumer{

	private Channel channel;
	
	public MyCustomer(Channel channel) {
		super(channel);
		this.channel = channel;
	}
	
	@Override
	public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
			throws IOException {
		System.err.println("-----------consume message----------");
		System.err.println("consumerTag: " + consumerTag);
		System.err.println("envelope: " + envelope);
		System.err.println("properties: " + properties);
		System.err.println("body: " + new String(body));
		
		channel.basicAck(envelope.getDeliveryTag(), false);
		
	}
	
}

三. SpringBoot整合rabbitMQ

用SpringBoot整合rabbitMQ时,写了两个工程,一个生产端,一个消费端。

工程为  rabbitmq-springboot-consumer 和 rabbitmq-springboot-producer

git地址:https://gitee.com/an592655791/rabbitMQ (不想贴代码了 -.-)

 

其中,在进行对象之间生产消费的时候,两个工程之间要用同一个包下的类,否则出现序列化出错的情况。

所以最好用json进行生产消费

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值