搜狐实验室 新闻点击量项目

44 篇文章 1 订阅
           新闻项目大数据实时可视化分析项目

项目托管到码云
第一章
技术点
Hadoop2.x、Zookeeper、Flume、Hive、Hbase、Kafka、Spark2.x、SparkStreaming、MySQL、Hue、J2EE、websoket、Echarts
需求分析
业务需求:
1)捕获用户浏览日志信息TB
2)实时分析前20名流量最高的新闻话题
3)实时统计当前线上已曝光的新闻话题
4)统计哪个时段用户浏览量最高
5)报表
架构图设计

流程图设计

集群架构

只有三台 不按这个来
Node2 Node3 Node4
HDFS namenode datanode Datanode
Yarn nodemanager nodemanager ResourceManager
zookeeper zookeeper zookeeper zookeeper
kafka kakfa kakfa kafka
habse Hbase 主 hbase Hbase master
flume flume flume flume
hive hive
mysql mysql
spark Spark master Spark spark
hue hue

第二章 Hadoop版本安装
Hadoop版本软件下载地址

https://archive.apache.org/dist
http://archive.cloudera.com/cdh5/

视频项目中 Hadoop版本
第三章 HBase搭建以及日志数据分析表创建
1、下载数据源文件

下载迷你版或者精简版本
下载好后上传到服务器

2、Hbase上创建表

create ‘weblogs’,‘info’
第四章 flume节点服务设计
使用flume 1.7
1台和2台负责收集节点数据
3台负责收集前两台数据的合并 并且分发 一个给kafka一个给hbase集群
在node3 和 node4 /opt/datas touch weblogs.log 用来web端实时写入数据的
Node3 和node4收集完数据后推给node2
1)node3节点配置flume,将数据采集到node2节点
vi flume-conf.properties
agent2.sources = r1
agent2.channels = c1
agent2.sinks = k1

agent2.sources.r1.type = exec
agent2.sources.r1.command = tail -F /opt/datas/weblogs.log
agent2.sources.r1.channels = c1

agent2.channels.c1.type = memory
agent2.channels.c1.capacity = 10000
agent2.channels.c1.transactionCapacity = 10000
agent2.channels.c1.keep-alive = 5

agent2.sinks.k1.type = avro
agent2.sinks.k1.channel = c1
agent2.sinks.k1.hostname =node2
agent2.sinks.k1.port = 5555
2)node4节点配置flume,将数据采集到node2节点
vi flume-conf.properties
agent3.sources = r1
agent3.channels = c1
agent3.sinks = k1

agent3.sources.r1.type = exec
agent3.sources.r1.command = tail -F /opt/datas/weblogs.log
agent3.sources.r1.channels = c1

agent3.channels.c1.type = memory
agent3.channels.c1.capacity = 10000
agent3.channels.c1.transactionCapacity = 10000
agent3.channels.c1.keep-alive = 5

agent3.sinks.k1.type = avro
agent3.sinks.k1.channel = c1
agent3.sinks.k1.hostname =node2
agent3.sinks.k1.port = 5555

配置完成
对于第一台
第五章 flume与kafka hbase集成
1、下载flume1.7源码

2、导入idea 只选择flume-ng-hbase-sink

导进来发现没有源代码
。。。
使用ctrl+shift+alt+s打开目录结构,选择module,再选择import module,选择项目的src目录即可

3、搜索对应的flume1.7 对应hbase-sink文档
http://flume.apache.org/releases/content/1.7.0/FlumeUserGuide.html
Flume与sink集成

黑体字必须指定
4、在node2 配置flume文件

vi flume-conf.properties
agent1.sources = r1
agent1.channels = kafkaC hbaseC
agent1.sinks = kafkaSink hbaseSink

agent1.sources.r1.type = avro
agent1.sources.r1.channels = hbaseC
agent1.sources.r1.bind =node2
agent1.sources.r1.port = 5555
agent1.sources.r1.threads = 5

agent1.channels.hbaseC.type = memory
agent1.channels.hbaseC.capacity = 100000
agent1.channels.hbaseC.transactionCapacity = 100000
agent1.channels.hbaseC.keep-alive = 20

agent1.sinks.hbaseSink.type = asynchbase
agent1.sinks.hbaseSink.table = weblogs
agent1.sinks.hbaseSink.columnFamily = info
agent1.sinks.hbaseSink.serializer =org.apache.flume.sink.hbase.KfkAsyncHbaseEventSerializer
agent1.sinks.hbaseSink.channel = hbaseC
agent1.sinks.hbaseSink.serializer.payloadColumn = datatime,userid,searchname,retorder,cliorder,cliurl

5、修改导入源码的部分代码
1、修改生成rowkey方式
进入SimpleRowKeyGenerator.java 里面
添加
public static byte[] getKfkRowKey(String userid, String datetime) throws UnsupportedEncodingException {
return (userid+datetime+ String.valueOf(System.currentTimeMillis())).getBytes(“UTF8”);
}
2、复制SimpleAsyncHbaseEventSerializer.java 重命名一下
KfkAsyncHbaseEventSerializer.java
修改KfkAsyncHbaseEventSerializer.java getAction()代码
@Override
public List getActions() {
List actions = new ArrayList();
if (payloadColumn != null) {
byte[] rowKey;
try {
switch (keyType) {
case TS:
rowKey = SimpleRowKeyGenerator.getTimestampKey(rowPrefix);
break;
case TSNANO:
rowKey = SimpleRowKeyGenerator.getNanoTimestampKey(rowPrefix);
break;
case RANDOM:
rowKey = SimpleRowKeyGenerator.getRandomKey(rowPrefix);
break;
default:
rowKey = SimpleRowKeyGenerator.getUUIDKey(rowPrefix);
break;
}
//解析列字段名
String[] columns=String.valueOf(this.payloadColumn).split(",");
//解析flume采集过来的每行的值
String[] values=String.valueOf(this.payload).split(",");
for (int i = 0; i < columns.length; i++) {//有六列 循环一下
byte[] colColumn=columns[i].getBytes();//列名
byte[] colValue=values[i].getBytes(Charsets.UTF_8);//value组
//数据校验,字段和值是否对应
if(colColumn.length!=colValue.length) break;//如果参数长度 和值长度不等
//时间
String datetime=values[0].toString();
//用户id
String userid=values[1].toString();
//根据用户自定义Rowkey
rowKey=SimpleRowKeyGenerator.getKfkRowKey(userid,datetime);

            PutRequest putRequest =  new PutRequest(table, rowKey, cf,
                    colColumn, colValue);
            actions.add(putRequest);
        }
    } catch (Exception e) {
        throw new FlumeException("Could not get row key!", e);
    }
}
return actions;

}

6、修改获取rowkey方式
需要将刚才编辑的指定默认:
编辑AsyncHBaseSink,查找SimpleAsyncHbaseEventSerializer,改为KfkSimpleAsyncHbaseEventSerializer,如下
if (eventSerializerType == null || eventSerializerType.isEmpty()) {
eventSerializerType =
“org.apache.flume.sink.hbase.KfkAsyncHbaseEventSerializer”;
logger.info(“No serializer defined, Will use default”);
}

7、修改服务器源文件
1)将文件中的tab更换成逗号
cat weblog.log|tr “\t” “,” > weblog2.log
2)将文件中的空格更换成逗号
cat weblog2.log|tr " " “,” > weblog3.log
改名 finalWeblog.log

8、导出自定义jar包
1)在idea工具中,选择File——》ProjectStructrue

2)左侧选中Artifacts,然后点击右侧的+号,最后选择JAR——》From modules with dependencies

3)然后直接点击ok

4)删除其他依赖包,只把flume-ng-hbase-sink打成jar包就可以了。

只留下
5)然后依次点击apply,ok

6)点击build进行编译,会自动打成jar包

7)到项目的\apache-flume-1.7.0-src\flume-ng-sinks\flume-ng-hbase-sink\out\artifacts\flume_ng_hbase_sink_jar 包

8)将打包名字替换为flume自带的包名flume-ng-hbase-sink-1.7.0.jar ,然后上传至flume/lib目录下,覆盖原有的jar包即可。
我先把node4节点的jar包改成bak 然后上传新包
Scp到node2节点一下
9、编辑node2添加kafka配置
完整配置如下
agent1.sources = r1
agent1.channels = kafkaC hbaseC
agent1.sinks = kafkaSink hbaseSink

agent1.sources.r1.type = avro
agent1.sources.r1.channels = hbaseC kafkaC
agent1.sources.r1.bind = node2
agent1.sources.r1.port = 5555
agent1.sources.r1.threads = 5

agent1.channels.hbaseC.type = memory
agent1.channels.hbaseC.capacity = 100000
agent1.channels.hbaseC.transactionCapacity = 100000
agent1.channels.hbaseC.keep-alive = 20

agent1.sinks.hbaseSink.type = asynchbase
agent1.sinks.hbaseSink.table = weblogs
agent1.sinks.hbaseSink.columnFamily = info
agent1.sinks.hbaseSink.serializer = org.apache.flume.sink.hbase.KfkAsyncHbaseEventSerializer
agent1.sinks.hbaseSink.channel = hbaseC
agent1.sinks.hbaseSink.serializer.payloadColumn = datatime,userid,searchname,retorder,cliorder,cliurl
#flume+Kafka******

agent1.channels.kafkaC.type = memory
agent1.channels.kafkaC.capacity = 100000
agent1.channels.kafkaC.transactionCapacity = 100000
agent1.channels.kafkaC.keep-alive = 20

agent1.sinks.kafkaSink.channel = kafkaC
agent1.sinks.kafkaSink.type = org.apache.flume.sink.kafka.KafkaSink
agent1.sinks.kafkaSink.brokerList = node2:9092,node3:9092,node4:9092
agent1.sinks.kafkaSink.topic = test
agent1.sinks.kafkaSink.zookeeperConnect = node2:2181,node3:2181,node4:2181
agent1.sinks.kafkaSink.requiredAcks = 1
agent1.sinks.kafkaSink.batchSize = 1
agent1.sinks.kafkaSink.serializer.class= = kafka.serializer.StringEncoder

注释部分:上面kafka集成根据官网1.7版本的flume已经

agent1.channels.kafkaC.type = memory
agent1.channels.kafkaC.capacity = 100000
agent1.channels.kafkaC.transactionCapacity = 100000
agent1.channels.kafkaC.keep-alive = 20

agent1.sinks.kafkaSink.channel = kafkaC
agent1.sinks.kafkaSink.type = org.apache.flume.sink.kafka.KafkaSink
agent1.sinks.kafkaSink.kafka.bootstrap.servers = node2:9092,node3:9092,node4:9092
agent1.sinks.kafkaSink.kafka.topic = weblogs
agent1.sinks.kafkaSink.zookeeperConnect = node2:2181,node3:2181,node4:2181
agent1.sinks.kafkaSink.kafka.producer.acks = 1
agent1.sinks.kafkaSink.kafka.flumeBatchSize = 100
agent1.sinks.kafkaSink.serializer.class= = kafka.serializer.StringEncoder
第六章 数据采集/存储/分布式完整流程测试
1、流程概览

2、idea工具构建程序开发生成模拟程序
新建一个java项目 source里面添加 main java添加相应的java文件打jar包

1、将日志包上传到服务器 分发到另外两个节点/opt/jars

2.编写运行模拟程序的shell脚本
1)在node3节点的/opt/datas目录下,创建weblog-shell.sh脚本。
vi weblog-shell.sh
#/bin/bash
echo “start log…”
#第一个参数是原日志文件,第二个参数是日志生成输出文件
java -jar /opt/jars/weblogs.jar /opt/datas/weblog.log /opt/datas/weblog-flume.log
修改weblog-shell.sh可执行权限
chmod 777 weblog-shell.sh
2)将node3节点上的/opt/datas/目录拷贝到node4节点
scp -r /opt/datas/ node4:/opt/datas/
3)修改node3和node4节点上面日志采集文件路径。以node3节点为例。
vi flume-conf.properties
agent2.sources = r1
agent2.channels = c1
agent2.sinks = k1

agent2.sources.r1.type = exec
#修改采集日志文件路径,node3节点也是修改此处
agent2.sources.r1.command = tail -F /opt/datas/weblog-flume.log
agent2.sources.r1.channels = c1

agent2.channels.c1.type = memory
agent2.channels.c1.capacity = 10000
agent2.channels.c1.transactionCapacity = 10000
agent2.channels.c1.keep-alive = 5

agent2.sinks.k1.type = avro
agent2.sinks.k1.channel = c1
agent2.sinks.k1.hostname = node2
agent2.sinks.k1.port = 5555
3、编写启动flume服务程序的shell脚本
1.在node3节点的flume安装目录下编写flume启动脚本。
vi flume-kfk-start.sh
#/bin/bash
echo “flume-2 start …”
bin/flume-ng agent --conf conf -f conf/flume-conf.properties -n agent2 -Dflume.root.logger=INFO,console
2.在node4节点的flume安装目录下编写flume启动脚本。
vi flume-kfk-start.sh
#/bin/bash
echo “flume-3 start …”
bin/flume-ng agent --conf conf -f conf/flume-conf.properties -n agent3 -Dflume.root.logger=INFO,console
3.在node2节点的flume安装目录下编写flume启动脚本。
vi flume-kfk-start.sh
#/bin/bash
echo “flume-node2 start …”
bin/flume-ng agent --conf conf -f conf/flume-conf.properties -n agent1 -Dflume.root.logger=INFO,console
4、编写Kafka Consumer执行脚本 kafka版本貌似不行了 我的0.8.2需要装个高版本 改成0.9版本的
1.在node2节点的Kafka安装目录下编写Kafka Consumer执行脚本
vi kfk-test-consumer.sh
#/bin/bash
echo “kfk-kafka-consumer.sh start …”
bin/kafka-console-consumer.sh --zookeeper node2:2181,node3:2181,node4:2181 --from-beginning --topic weblogs
2.将kfk-test-consumer.sh脚本分发另外两个节点
scp kfk-test-consumer.sh node3:pwd

5、启动模拟程序并测试
在node3节点启动日志产生脚本,模拟产生日志是否正常。
/opt/datas/weblog-shell.sh

6、启动数据采集所有服务
1.启动Zookeeper服务
bin/zkServer.sh start
2.启动hdfs服务
sbin/start-dfs.sh
3.启动HBase服务
bin/start-hbase.sh
创建hbase业务表
bin/hbase shell
create ‘weblogs’,‘info’
4.启动Kafka服务
bin/kafka-server-start.sh config/server.properties &
创建业务数据topic
bin/kafka-topics.sh --zookeeper node2:2181,node3:2181,node4:2181, --create --topic weblogs --replication-factor 3 --partitions 3
5.配置flume相关环境变量
vi flume-env.sh
export JAVA_HOME=/usr/java/jdk1.8.0_171-amd64
export HADOOP_HOME= /opt/tkk/hadoop-2.6.5
export HBASE_HOME= /root/hbase/hbase-1.2.1

6、完成数据采集全流程测试
1.在node2节点上启动flume聚合脚本,将采集的数据分发到Kafka集群和hbase集群。
./flume-kfk-start.sh
2.在node3节点上完成数据采集
1)使用shell脚本模拟日志产生
cd /opt/datas/
./weblog-shell.sh
2)启动flume采集日志数据发送给聚合节点
./flume-kfk-start.sh
3.在node4节点上完成数据采集
1)使用shell脚本模拟日志产生
cd /opt/datas/
./weblog-shell.sh
2)启动flume采集日志数据发送给聚合节点
./flume-kfk-start.sh
4.启动Kafka Consumer查看flume日志采集情况
bin/kafka-console-consumer.sh --zookeeper node2:2181 --topic weblogs --from-beginning

迫于联调不知道为什么一直出错 现在kafka拉取数据采用直接在主机三进行单机测试 可以发送数据 也可以正常消费
./kafka-console-consumer.sh --zookeeper node2:2181,node3:2181,node4:2181 --topic weblogs

5.查看hbase数据写入情况

./hbase-shell
count ‘weblogs’

第七章 hive安装下载
https://archive.apache.org/dist/hive/
他下的0.13.1

Hive与hbase整合
1、进入hive wiki

2、搜索hbase
集成搜索
https://cwiki.apache.org/confluence/display/Hive/HBaseIntegration
hive 与hbase集成
1.在hive-site.xml文件中配置Zookeeper,hive通过这个参数去连接HBase集群。

hbase.zookeeper.quorum node2,node3,node4

2.将hbase的8个包拷贝到hive/lib目录下。如果是CDH版本,已经集成好不需要导包。
export HBASE_HOME=/root/hbase/hbase-1.2.1
export HIVE_HOME=/root/hiveSoft/apache-hive-1.2.1-bin
ln -s $HBASE_HOME/lib/hbase-server-1.2.1.jar $HIVE_HOME/lib/hbase-server-1.2.1.jar
ln -s $HBASE_HOME/lib/hbase-client-1.2.1.jar $HIVE_HOME/lib/hbase-client-1.2.1.jar
ln -s $HBASE_HOME/lib/hbase-protocol-1.2.1.jar $HIVE_HOME/lib/hbase-protocol-1.2.1.jar
ln -s $HBASE_HOME/lib/hbase-it-1.2.1.jar $HIVE_HOME/lib/hbase-it-1.2.1.jar
ln -s $HBASE_HOME/lib/htrace-core-3.1.0-incubating.jar $HIVE_HOME/lib/htrace-core-3.1.0-incubating.jar
ln -s $HBASE_HOME/lib/hbase-hadoop2-compat-1.2.1.jar $HIVE_HOME/lib/hbase-hadoop2-compat-1.2.1.jar
ln -s $HBASE_HOME/lib/hbase-hadoop-compat-1.2.1.jar $HIVE_HOME/lib/hbase-hadoop-compat-1.2.1.jar
ln -s $HBASE_HOME/lib/hbase-common-1.2.1.jar $HIVE_HOME/lib/hbase-common-1.2.1.jar
3.创建与HBase集成的Hive的外部表
create external table weblogs(id string,datatime string,userid string,searchname string,retorder string,cliorder string,cliurl string) STORED BY ‘org.apache.hadoop.hive.hbase.HBaseStorageHandler’ WITH SERDEPROPERTIES(“hbase.columns.mapping” = “:key,info:datatime,info:userid,info:searchname,info:retorder,info:cliorder,info:cliurl”) TBLPROPERTIES(“hbase.table.name” = “weblogs”);

#查看hbase数据记录
select count(*) from weblogs;

第八章 Structured Streaming
https://databricks.com/?s=Real-Time+End-to-End
1、 以上帖子是kafka与structured streaming结合案例
2、 数据源kafka—>业务流数据 weblogs
结构化处理kafka的数据
3、 处理结果写入mysqlq数据库
4、 过滤:desc count limit 20
5、 Mysql操作 连接mysql

1、 创建数据库
test.webCount
输出 titleName,count
过滤:desc count limit 20
Web系统

Create table webCount (titleName varchar(255) CHARACTER SET utf8 DEFAULT NULL,
count int (11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2、启动linux kafak以及mysql

6、 代码部分
① Structured Streaming部分代码
package com.scala.weblog

import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.streaming.ProcessingTime

object StructuredStreaming {
//定义数据库表对应实体类
case class Weblog(datatime:String,
userid:String,
searchname:String,
retorder:String,
cliorder:String,
cliurl:String)
def main(args: Array[String]): Unit = {

   /* val spark  = SparkSession.builder()
        .master("local[*]")
        .appName("streaming").getOrCreate()

    val df = spark
        .readStream
        .format("kafka")
        .option("kafka.bootstrap.servers", "node2:9092")
        .option("subscribe", "weblogs")
        .load()

    import spark.implicits._
    val lines = df.selectExpr("CAST(value AS STRING)").as[String]
    val wordCounts=lines.flatMap(_.split(" ")).groupBy("value").count()

    val url ="jdbc:mysql://node3:3306/test"
    val username="root"
    val password="123456"


    val query = wordCounts.writeStream
        .outputMode("update")
        .format("console")

        .start()
    query.awaitTermination()*/



    val spark  = SparkSession.builder()
        .master("local[2]")
        .appName("streaming").getOrCreate()

    val df = spark
        .readStream
        .format("kafka")
        .option("kafka.bootstrap.servers", "node2:9092,node3:9092,node4:9092")
        .option("subscribe", "weblogs")
        .load()
    import spark.implicits._
    val lines = df.selectExpr("CAST(value AS STRING)").as[String]
    val weblog = lines.map(_.split(","))
        .map(x => Weblog(x(0),x(1), x(2),x(3),x(4),x(5)))
    val titleCount = weblog
        .groupBy("searchname").count().toDF("titleName","count")

    val url ="jdbc:mysql://node3:3306/test"
    val username="root"
    val password="123"

    val writer = new JDBCSink(url,username,password)
    val query = titleCount.writeStream
        .foreach(writer)
        .outputMode("update")
        .trigger(ProcessingTime("25 seconds"))
        .start()
    query.awaitTermination()
}

}
② 数据库连接池
package com.scala.weblog

/**

  • 数据库连接池
    */

import java.sql.{Connection, DriverManager}
import java.util

class MySqlPool(url:String, user:String, pwd:String) extends Serializable{
private val max = 3 //连接池连接总数
private val connectionNum = 1 //每次产生连接数
private var conNum = 0 //当前连接池已产生的连接数
private val pool = new util.LinkedListConnection //连接池

//获取连接
def getJdbcConn() : Connection = {
    //同步代码块
    AnyRef.synchronized({
        if(pool.isEmpty){
            //加载驱动
            preGetConn()
            for(i <- 1 to connectionNum){
                val conn = DriverManager.getConnection(url,user,pwd)
                pool.push(conn)
                conNum +=  1
            }
        }
        pool.poll()
    })
}

//释放连接
def releaseConn(conn:Connection): Unit ={
    pool.push(conn)
}
//加载驱动
private def preGetConn() : Unit = {
    //控制加载
    if(conNum < max && !pool.isEmpty){
        println("Jdbc Pool has no connection now, please wait a moments!")
        Thread.sleep(2000)
        preGetConn()
    }else{
        Class.forName("com.mysql.jdbc.Driver");
    }
}

}
③ Jdbc连接
package com.scala.weblog

import java.sql.{Connection, ResultSet, SQLException, Statement}

import org.apache.spark.sql.{ForeachWriter, Row}

/**

  • 数据库连接
    */
    class JDBCSink(url:String, username:String,password:String) extends ForeachWriter[Row]{

    var statement : Statement =_
    var resultSet : ResultSet =_
    var connection : Connection=_
    override def open(partitionId: Long, version: Long): Boolean = {
    connection = new MySqlPool(url,username,password).getJdbcConn();
    statement = connection.createStatement()
    return true
    }

    override def process(value: Row): Unit = {
    val titleName = value.getAsString.replaceAll("[\[\]]","")
    val count = value.getAsLong;

    val querySql = "select 1 from webCount " +
        "where titleName = '"+titleName+"'"
    
    val updateSql = "update webCount set " +
        "count = "+count+" where titleName = '"+titleName+"'"
    
    val insertSql = "insert into webCount(titleName,count)" +
        "values('"+titleName+"',"+count+")"
    
    try{
    
        var resultSet=statement.executeQuery(querySql)
    
        if(resultSet.next()){
            statement.executeUpdate(updateSql)
        }else{
            statement.execute(insertSql)
        }
    }catch {
        case ex: SQLException => {
            println("SQLException")
        }
        case ex: Exception => {
            println("Exception")
        }
        case ex: RuntimeException => {
            println("RuntimeException")
        }
        case ex: Throwable => {
            println("Throwable")
        }
    }
    

    }

    override def close(errorOrNull: Throwable): Unit = {
    // if(resultSet.wasNull()){
    // resultSet.close()
    // }
    if(statementnull){
    statement.close()
    }
    if(connection
    null){
    connection.close()
    }
    }

}

7、 Mysql中文乱码 vi /etc/my.cnf

mysql> show variables like ‘character_set%’;
最后一章 配置web服务
1、 配置tomcat
第一步

第二步

第三步

2、 代码开发
加入tomcat lib包

3、 成果图

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值