利用spark分析慕课网千万条日志实战项目一、

具体分析流程链接:

https://blog.csdn.net/qq_41479464/article/details/98211937

  • 用户行为日志概述:
  • 用户行为日志:

用户每次访问网站时所有的行为数据(访问、浏览、搜索、点击...)用户行为轨迹、流量日志。

  • 日志数据内容:
  1. 访问的系统属性: 操作系统、浏览器等等
  2. 访问特征:点击的url、从哪个url跳转过来的(referer)、页面上的停留时间等
  3. 访问信息:session_id、访问ip(访问城市)等
  • 日志举例:

格式:访问时间 访问内容网址 从哪个浏览器跳转 session_id 访问ip

2013-05-19 13:00:00     http://www.taobao.com/17/?tracker_u=1624169&type=1      B58W48U4WKZCJ5D1T3Z9ZY88RU7QA7B1        http://hao.360.cn/      1.196.34.243  

拿到数据之后:

一般的日志处理方式,我们是需要进行分区的,

按照日志中的访问时间进行相应的分区,比如:d,h,m5(每5分钟一个分区)

输入:访问时间、访问URL、耗费的流量、访问IP地址信息

输出:URL、cmsType(video/article)、cmsId(编号)、流量、ip、城市信息、访问时间、天

 

Imooc网主站日志介绍:

  1. 访问时间
  2. 访问url
  3. 访问过程耗费的流量
  4. 访问ip地址

第一步是数据清洗:

 

一般的日志处理方式,我们是需要进行分区的,

按照日志中的访问时间进行相应的分区,比如:d,h,m5(每5分钟一个分区)

输入:访问时间、访问URL、耗费的流量、访问IP地址信息

输出:URL、cmsType(video/article)、cmsId(编号)、流量、ip、城市信息、访问时间、天

 

利用spark分析慕课网5G日志实战项目

  1. 需求一:统计imooc的主站最受欢迎的课程/或手记的TopN访问次数

首先对数据进行读取,然后对其中相应的数据格式进行转换

整体项目结构:

整体pom文件:

 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com</groupId>
  <artifactId>liang</artifactId>
  <version>1.0</version>
  <inceptionYear>2008</inceptionYear>


  <properties>
    <maven.compiler.source>1.5</maven.compiler.source>
    <maven.compiler.target>1.5</maven.compiler.target>
    <encoding>UTF-8</encoding>
    <scala.version>2.11.8</scala.version>
    <spark.version>2.1.0</spark.version>
  </properties>

  <repositories>
    <repository>
      <id>scala-tools.org</id>
      <name>Scala-Tools Maven2 Repository</name>
      <url>http://scala-tools.org/repo-releases</url>
    </repository>
  </repositories>

  <pluginRepositories>
    <pluginRepository>
      <id>scala-tools.org</id>
      <name>Scala-Tools Maven2 Repository</name>
      <url>http://scala-tools.org/repo-releases</url>
    </pluginRepository>
  </pluginRepositories>

  <dependencies>

    <!--scala-->
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala-library</artifactId>
      <version>${scala.version}</version>
      <!--
      <scope>provided</scope>
      -->
    </dependency>

    <!--SparkSQL-->
    <dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-sql_2.11</artifactId>
      <version>${spark.version}</version>
      <!--
      <scope>provided</scope>
      -->
    </dependency>

    <dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-hive_2.11</artifactId>
      <version>${spark.version}</version>
      <!--
      <scope>provided</scope>
      -->
    </dependency>

    <dependency>
      <groupId>org.spark-project.hive</groupId>
      <artifactId>hive-jdbc</artifactId>
      <version>1.2.1.spark2</version>
      <!--
      <scope>provided</scope>
      -->
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.38</version>
    </dependency>

    <dependency>
      <groupId>com.ggstar</groupId>
      <artifactId>ipdatabase</artifactId>
      <version>1.0</version>
    </dependency>

    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml</artifactId>
      <version>3.14</version>
    </dependency>

    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi</artifactId>
      <version>3.14</version>
    </dependency>

  </dependencies>

  <build>
    <sourceDirectory>src/main/scala</sourceDirectory>
    <testSourceDirectory>src/test/scala</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.scala-tools</groupId>
        <artifactId>maven-scala-plugin</artifactId>
        <version>2.15.0</version>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
            </goals>
            <configuration>
              <args>
                <arg>-dependencyfile</arg>
                <arg>${project.build.directory}/.scala_dependencies</arg>
              </args>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.6</version>
        <configuration>
          <useFile>false</useFile>
          <disableXmlReport>true</disableXmlReport>
          <!-- If you have classpath issue like NoDefClassError,... -->
          <!-- useManifestOnlyJar>false</useManifestOnlyJar -->
          <includes>
            <include>**/*Test.*</include>
            <include>**/*Suite.*</include>
          </includes>
        </configuration>
      </plugin>

      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <mainClass></mainClass>
            </manifest>
          </archive>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <reporting>
    <plugins>
      <plugin>
        <groupId>org.scala-tools</groupId>
        <artifactId>maven-scala-plugin</artifactId>
        <configuration>
          <scalaVersion>${scala.version}</scalaVersion>
        </configuration>
      </plugin>
    </plugins>
  </reporting>
</project>
数据清洗:

对于时间格式的数据处理:

[10/Nov/2016:00:01:02 +0800]

对于以上的时间的处理方式:

import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date

/**
 * 日期时间解析工具类
  * 10/Nov/2016:00:01:02 +0800
 */
object DateUtils {


    //输入文件日期格式
    //10/Nov/2016:00:01:02 +0800
    val YYYYMMDDHHMM_TIME_FORMAT = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z",Locale.ENGLISH)
    //目标文件日期格式
    val TARGET_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

    /**
      * 获取时间:yyyy-MM-dd HH:mm:ss
      */
    def parse(time:String)={
      TARGET_FORMAT.format(new Date(getTime(time)))
    }

    /**
      * 获取输入日志的时间
      * 日期格式
      *[10/Nov/2016:00:01:02 +0800]
      *
      */
    def getTime(time:String) ={

      try{
        YYYYMMDDHHMM_TIME_FORMAT.parse(time.substring(time.indexOf("[")+1,time.lastIndexOf("]"))).getTime
      }catch {
        case e:Exception =>{
          0l
        }
      }
    }

    def main(args: Array[String]){
      println(parse("[10/Nov/2016:00:01:02 +0800]"))
    }

}

转换结果如下:

 

我们使用上面 的日期转换格式会发现我们得出的结果可能有问题列如像下面这样的数据:

 

所以对于日期格式的转换工具类还需要改进:

日期转换格式二:

package com

import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date

import org.apache.commons.lang3.time.FastDateFormat

/**
 * 日期时间解析工具类
  * 10/Nov/2016:00:01:02 +0800
 */
object DateUtils {


    //输入文件日期格式
    //10/Nov/2016:00:01:02 +0800
    val YYYYMMDDHHMM_TIME_FORMAT = FastDateFormat.getInstance("dd/MMM/yyyy:HH:mm:ss Z",Locale.ENGLISH)
    //目标文件日期格式
    val TARGET_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")

    /**
      * 获取时间:yyyy-MM-dd HH:mm:ss
      */
    def parse(time:String)={
      TARGET_FORMAT.format(new Date(getTime(time)))
    }

    /**
      * 获取输入日志的时间
      * 日期格式
      *[10/Nov/2016:00:01:02 +0800]
      *
      */
    def getTime(time:String) ={
            //注意需要转换为Long类型的数据
      try{
        YYYYMMDDHHMM_TIME_FORMAT.parse(time.substring(time.indexOf("[")+1,time.lastIndexOf("]"))).getTime
      }catch {
        case e:Exception =>{//如果输入的时间异常:那么就是直接转换为0
          0l
        }
      }
    }

    def main(args: Array[String]){
      println(parse("[10/Nov/2016:00:01:02 +0800]"))
    }

}

然后观看数据就没有之前出现的情况了。原因:SimpleDateFormat 线程不安全。

整体得到时间,url,流量,ip地址、数据代码如下:

package com

import org.apache.spark.sql.SparkSession

/**
 * 第一步清洗:抽取出我们想要的指定列的数据
 */
object SparkStatFormat {

  def main(args: Array[String]) {

    val spark = SparkSession.builder().appName("SparkStatFormatJob").master("local[2]").getOrCreate()
    //读取数据
    val access = spark.sparkContext.textFile("D:\\BigDataTest\\data\\access.20161111.log")
    //打印出前面100条数据
    //    access.take(100).foreach(println)
    //按照空格进行数据分割
    access.map(line =>{
      val splits = line.split(" ")
      val ip = splits(0)//第一个为ip地址
      //根据断点调试可以看到时间在第几个split的数组当中
      /**
      * 原始日志的第三个和第四个字段拼接起来就是完整的访问时间
        * (183.162.52.7,[10/Nov/2016:00:01:02 +0800]) ==》yyyy-MM-dd HH:mm:ss
        * 这个时间需要时间解析
      **/
      val time = splits(3)+" "+splits(4)
      //拿取url,经过反复调试可以观察到split(11)为url
      //把不必要的引号替换为空的
      val url = splits(11).replaceAll("\"","")
      //流量
      val traffic = splits(9)

      //利用元组的形式存放
      //用写的时间转换的工具类来转换时间
//      (ip,DateUtils.parse(time),url,traffic)
      DateUtils.parse(time)+"\t"+url+"\t"+traffic+"\t"+ip
    }).saveAsTextFile("D:\\BigDataTest\\data\\output")


    spark.stop()
  }

}

写入磁盘得到的数据:因为太多了,所以自动分块写的。

 

拿到数据之后:

一般的日志处理方式,我们是需要进行分区的,

按照日志中的访问时间进行相应的分区,比如:d,h,m5(每5分钟一个分区)

数据的格式转换的工具类:

package log

import org.apache.spark.sql.Row
import org.apache.spark.sql.types.{LongType, StringType, StructField, StructType}

/**
  * 访问日志转换(输入==》输出)工具类
  *
  */
object AccessConvertUtil {
  /**
    * 定义输出数据类型的字段
    */
  val struct = StructType(
    Array(
      StructField("url",StringType),
      StructField("cmsType",StringType),
      StructField("cmsId",LongType),
      StructField("traffic",LongType),
      StructField("ip",StringType),
      StructField("city",StringType),
      StructField("time",StringType),
      StructField("day",StringType)
    )
  )
  /**
    * 是根据输入的每一行信息转换成输出的 样式
      @param log 输入的每一行记录信息
               2017-05-11 14:09:14 http://www.imooc.com/video/4500  304  218.75.35.226
    */
  def parseLog(log:String)={
    try {
      val splits = log.split("\t")
      val url = splits(1)
      val traffic = splits(2).toLong
      val ip = splits(3)
      val domain = "http://www.imooc.com/"
      val cms = url.substring(url.indexOf(domain)+domain.length)
      var cmsTypedId = cms.split("/")
      var cmsType = ""
      var cmsId = 0l

      if (cmsTypedId.length>1){
        cmsType = cmsTypedId(0)
        cmsId = cmsTypedId(1).toLong
      }
      val city = IpUtils.getCity(ip)
      val time  = splits(0)
      val day = time.substring(0,10).replaceAll("-","")

      Row(url,cmsType,cmsId,traffic,ip,city,time,day)
    }catch {
      case e: Exception=>Row(0)
    }
  }
}

 

输入:访问时间、访问URL、耗费的流量、访问IP地址信息

输出:URL、cmsType(video/article)、cmsId(编号)、流量、ip、城市信息、访问时间、天

Ip地址转换使用github上已有的开源项目

1)git clone https://github.com/wzhe06/ipdatabase.git

2)编译下载的项目:mvn clean package -DskipTests

3)安装jar包到自己的maven仓库

mvn install:install-file-Dfile=D:\BigDataTest\test\ipdatabase-master\target\ipdatabase-1.0-SNAPSHOT.jar -DgroupId=com.ggstar -DartifactId=ipdatabase -Dversion=1.0 -Dpackaging=jar

java.io.FileNotFoundException:

file:/Users/rocky/maven_repos/com/ggstar/ipdatabase/1.0/ipdatabase-1.0.jar!/ipRegion.xlsx (No such file or directory)

把两个文件复制到resources里面:

 

然后pom文件中加入:

<dependency>
  <groupId>com.ggstar</groupId>
  <artifactId>ipdatabase</artifactId>
  <version>1.0</version>
</dependency>

<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml</artifactId>
  <version>3.14</version>
</dependency>

<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi</artifactId>
  <version>3.14</version>
</dependency>

 

Ip地址转换的工具类:

import com.ggstar.util.ip.IpHelper
/**
  * IP解析工具类
  */
object IpUtils {
  def getCity(ip:String)={
    IpHelper.findRegionByIp(ip)
  }
  def main(args: Array[String]): Unit = {
    println(getCity("218.22.9.56"))
  }
}



得到想要的输出数据:SparkStaticCleanJob

package log

import org.apache.spark.sql.{SaveMode, SparkSession}
/**
  * 使用spark完成我们的是数据清洗操作
  *
  */
object SparkStaticCleanJob {
  def main(args: Array[String]):Unit= {
    val spark = SparkSession.builder().appName("SparkStaticCleanJob").master("local[2]").getOrCreate()
    val accessRDD = spark.sparkContext.textFile("D:\\BigDataTest\\data\\access.log")
//    accessRDD.take(10).foreach(println)
    val accessDF = spark.createDataFrame(accessRDD.map(x => AccessConvertUtil.parseLog(x)),
     AccessConvertUtil.struct)
//        accessDF.printSchema()
//        accessDF.show(false)
    //coalesce(1)写入一个文件
    accessDF.coalesce(1).write.format("parquet").mode(SaveMode.Overwrite).partitionBy("day").save("D:\\BigDataTest\\data\\clean")
    spark.stop()

  }
}

 

最受欢迎的topN的课程个数的统计:

创建一张表留着存放数据库中

create table day_video_access_topn_stat (

day varchar(8) not null,

cms_id bigint(10) not null,

times bigint(10) not null,

primary key (day, cms_id)

);

数据库工具类代码:

package log

import java.sql.DriverManager

import com.mysql.jdbc.{Connection, PreparedStatement}

/**
  * Mysql操作工具类
  */
object MysqlUtils {
  def getConnection()={
    DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc_project?user=root&password=root")


  }
  /**
    * 释放数据库连接等资源
    * @param connection
    * @param pstmt
    */
  def release(connection: Connection, pstmt: PreparedStatement): Unit = {
    try {
      if (pstmt != null) {
        pstmt.close()
      }
    } catch {
      case e: Exception => e.printStackTrace()
    } finally {
      if (connection != null) {
        connection.close()
      }
    }
  }
  def main(args: Array[String]): Unit = {
    println(getConnection())
  }
}

然后写一个实体类的代码:

package log

/**
  * 每天课程访问次数实体类
  * @param day
  * @param cmsId
  * @param times
  */
case class DayVideoAccessStat (day:String,cmsId:Long,times:Long){

}

一个StatDao层就是我需要的插入数据库的逻辑层:

package log

import java.sql.{Connection, PreparedStatement}

import scala.collection.mutable.ListBuffer

/**
  * 各个维度统计的Dao操作
  */
object StatDao {
  /**
    * 批量保存到DayVideoAccessStat数据库中
    *
    */

  /**
    * 批量保存DayVideoAccessStat到数据库
    */
  def insertDayVideoAccessTopN(list: ListBuffer[DayVideoAccessStat]): Unit = {

    var connection: Connection = null
    var pstmt: PreparedStatement = null

    try {
      connection = MysqlUtils.getConnection()

      connection.setAutoCommit(false) //设置手动提交

      val sql = "insert into day_video_access_topn_stat(day,cms_id,times) values (?,?,?) "
      pstmt = connection.prepareStatement(sql)

      for (ele <- list) {
        pstmt.setString(1, ele.day)
        pstmt.setLong(2, ele.cmsId)
        pstmt.setLong(3, ele.times)

        pstmt.addBatch()
      }

      pstmt.executeBatch() // 执行批量处理
      connection.commit() //手工提交
    } catch {
      case e: Exception => e.printStackTrace()
    } finally {
      MysqlUtils.release(connection,pstmt)
    }
  }
}

 

统计topN的课程数插入到数据库中的代码:

package log

import org.apache.spark.sql.{DataFrame, SparkSession}

import scala.collection.mutable.ListBuffer

/**
  * TopN统计spark作业
  */
object TopNStatJob {
  def main(args: Array[String]) {
    val spark = SparkSession.builder().appName("TopNStatJob")
      .config("spark.sql.sources.partitionColumnTypeInference.enabled",false)
      .master("local[2]").getOrCreate()
    val accessDF = spark.read.format("parquet").load("D:\\BigDataTest\\data\\clean")
    accessDF.printSchema()
    accessDF.show()
    //最受欢迎的topN的课程个数
    videoAccessTopNstat(spark,accessDF)

    spark.stop()
  }
  /**
    * 最受欢迎的topN的课程个数
    * @param spark
    * @param accessDF
    */
  def videoAccessTopNstat(spark: SparkSession,accessDF: DataFrame): Unit ={
    /**
      * 使用DataFrame的方式进行统计
      */
    //    //隐式转换
//    import spark.implicits._
//    //过滤出当天的视频的个数
//    val videoAccessTopNDF = accessDF.filter($"day"==="20170511" && $"cmsType"==="video")
//      .groupBy("day","cmsId")
//      .agg(count("cmsId")as("times"))
//      .orderBy($"times".desc)
//    videoAccessTopNDF.show(false)

    //方法二采用sparkSql的方式
    accessDF.createOrReplaceTempView("access_log")
   val videoAccessTopNDF = spark.sql("select day,cmsId,count(1) as times from access_log "
      + "where day='20170511' and cmsType= 'video' "
      + "group by day,cmsId order by times desc")
   videoAccessTopNDF.show(false)

    /**
      * 将统计结果写入到MySQL中
      */
    try {
      videoAccessTopNDF.foreachPartition(partitionOfRecords => {
        val list = new ListBuffer[DayVideoAccessStat]

        partitionOfRecords.foreach(info => {
          val day = info.getAs[String]("day")
          val cmsId = info.getAs[Long]("cmsId")
          val times = info.getAs[Long]("times")
          /**
            * 不建议大家在此处进行数据库的数据插入
            */
          list.append(DayVideoAccessStat(day, cmsId, times))
        })
          StatDao.insertDayVideoAccessTopN(list)
      })
    } catch {
      case e:Exception => e.printStackTrace()
    }
  }
}

下面得出的结果:按照某天分析的:

 

 

 

数据库中的数据如下:

现在需要把 这些我们想要得到的数据存放在数据库中,留后期数据可视化使用:

存放进数据库的

 

 

修改一个参数就是手记的数据了

 

 

手记和课程的对比:

 

 

1 使用ECharts可视化框架
1)基本使用

ECharts使用教程

2)静态测试

将下载下来的文件echarts.min.js放在js文件夹下
在webapp下创建test.html
test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ECharts USE</title>
    <!-- 引入 ECharts 文件 -->
    <script src="js/echarts.min.js"></script>
</head>
<body>

<!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
    // 基于准备好的dom,初始化echarts实例
    var myChart = echarts.init(document.getElementById('main'));

    // 指定图表的配置项和数据
    var option = {
        title: {
            text: '某站点用户访问来源',
            subtext: '纯属虚构',
            x: 'center'
        },
        tooltip: {
            trigger: 'item',
            formatter: "{a} <br/>{b} : {c} ({d}%)"
        },
        legend: {
            orient: 'vertical',
            left: 'left',
            data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']
        },
        series: [
            {
                name: '访问来源',
                type: 'pie',
                radius: '55%',
                center: ['50%', '60%'],
                data: [
                    {value: 335, name: '直接访问'},
                    {value: 310, name: '邮件营销'},
                    {value: 234, name: '联盟广告'},
                    {value: 135, name: '视频广告'},
                    {value: 1548, name: '搜索引擎'}
                ],
                itemStyle: {
                    emphasis: {
                        shadowBlur: 10,
                        shadowOffsetX: 0,
                        shadowColor: 'rgba(0, 0, 0, 0.5)'
                    }
                }
            }
        ]
    };

    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option);
</script>
</body>
</html>

结果: 

 

3)创建工程,添加依赖,编写代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.liang.spark</groupId>
  <artifactId>web</artifactId>
  <version>1.0</version>
  <packaging>war</packaging>

  <name>web Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.0</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.38</version>
    </dependency>

    <dependency>
      <groupId>net.sf.json-lib</groupId>
      <artifactId>json-lib</artifactId>
      <version>2.4</version>
      <classifier>jdk15</classifier>
    </dependency>



  </dependencies>

  <build>
    <finalName>web</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

 VideoAccessTopN.java

 

package com.liang.domain;

public class VideoAccessTopN {

    private String name;
    private long value ;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getValue() {
        return value;
    }

    public void setValue(long value) {
        this.value = value;
    }
}

MySQLUtils.java 

 

package com.liang.utils;

import java.sql.*;

/**
 * 操作MySQL的工具类
 */
public class MySQLUtils {

    private static final String USERNAME = "root";

    private static final String PASSWORD = "root";

    private static final String DRIVERCLASS = "com.mysql.jdbc.Driver";

    private static final String URL = "jdbc:mysql://localhost:3306/imooc_project";


    /**
     * 获取数据库连接
     */
    public static Connection getConnection() {
        Connection connection = null;
        try {
            Class.forName(DRIVERCLASS);
            connection = DriverManager.getConnection(URL,USERNAME,PASSWORD);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return connection;
    }


    /**
     * 释放资源
     */
    public static void release(Connection connection, PreparedStatement pstmt, ResultSet rs) {
        if(rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if(pstmt != null) {
            try {
                pstmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if(connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        System.out.println(getConnection());
    }

}

 VideoAccessTopNDAO.java

package com.liang.dao;

import com.liang.domain.VideoAccessTopN;
import com.liang.utils.MySQLUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 面向接口编程
 */
public class VideoAccessTopNDAO {


    static Map<String,String> courses = new HashMap<String,String>();
    static {
        courses.put("4000", "MySQL优化");
        courses.put("4500", "Crontab");
        courses.put("4600", "Swift");
        courses.put("14540", "SpringData");
        courses.put("14704", "R");
        courses.put("14390", "机器学习");
        courses.put("14322", "redis");
        courses.put("14390", "神经网络");
        courses.put("14623", "Docker");
    }

    /**
     * 根据课程编号查询课程名称
     */
    public String getCourseName(String id) {
        return courses.get(id);
    }


    /**
     * 根据day查询当天的最受欢迎的Top5课程
     * @param day
     */
    public List<VideoAccessTopN> query(String day) {
        List<VideoAccessTopN> list = new ArrayList<VideoAccessTopN>();

        Connection connection = null;
        PreparedStatement psmt = null;
        ResultSet rs = null;

        try {
            connection = MySQLUtils.getConnection();
            String sql = "select cms_id ,times  from  day_video_access_topn_stat where day =? order by times desc limit 5";
            psmt = connection.prepareStatement(sql);
            psmt.setString(1, day);

            rs = psmt.executeQuery();

            VideoAccessTopN domain = null;
            while(rs.next()) {
                domain = new VideoAccessTopN();
                /**
                 * TODO... 在页面上应该显示的是课程名称,而我们此时拿到的是课程编号
                 *
                 * 如何根据课程编号去获取课程名称呢?
                 * 编号和名称是有一个对应关系的,一般是存放在关系型数据库
                 */
                domain.setName(getCourseName(rs.getLong("cms_id")+""));
                domain.setValue(rs.getLong("times"));

                list.add(domain);
            }

        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            MySQLUtils.release(connection, psmt, rs);
        }
        return list;
    }

    public static void main(String[] args) {
        VideoAccessTopNDAO dao = new VideoAccessTopNDAO();
        List<VideoAccessTopN> list = dao.query("20170511");
        for(VideoAccessTopN result: list) {
            System.out.println(result.getName() + " , " + result.getValue());
        }
    }

}

 VideoAccessTopNServlet.java

 

package com.liang.web;

import com.liang.dao.VideoAccessTopNDAO;
import com.liang.domain.VideoAccessTopN;
import net.sf.json.JSONArray;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

/**
 * 最受欢迎的TOPN课程
 *
 * Web ==> Service ==> DAO
 */
public class VideoAccessTopNServlet extends HttpServlet{

    private VideoAccessTopNDAO dao;

    @Override
    public void init() throws ServletException {
        dao = new VideoAccessTopNDAO();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String day = req.getParameter("day");

        List<VideoAccessTopN> results =  dao.query(day);
        JSONArray json = JSONArray.fromObject(results);

        resp.setContentType("text/html;charset=utf-8");

        PrintWriter writer = resp.getWriter();
        writer.println(json);
        writer.flush();
        writer.close();

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

 

前端代码

在js文件夹下放入文件jquery.js 

topn.html

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Echarts HelloWorld</title>

    <!-- 引入 ECharts 文件 -->
    <script src="js/echarts.min.js"></script>
    <SCRIPT src="js/jquery.js"></SCRIPT>
</head>
<body>

<!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->
<div id="main" style="width: 800px;height:600px;"></div>

<script type="text/javascript">
    // 基于准备好的dom,初始化echarts实例
    var myChart = echarts.init(document.getElementById('main'));

    // 指定图表的配置项和数据
    var option = {
        title : {
            text: '主站最受欢迎的TopN课程',
            x:'center'
        },
        tooltip : {
            trigger: 'item',
            formatter: "{a} <br/>{b} : {c} ({d}%)"
        },
        legend: {
            orient: 'vertical',
            left: 'left',
            data:(function(){
                var courses = [];
                $.ajax({
                    type:"GET",
                    url:"/stat?day=20170511",
                    dataType:'json',
                    async:false,
                    success:function(result) {
                        for(var i=0;i<result.length; i++){
                            courses.push({"value": result[i].value,"name":result[i].name});
                        }
                    }
                })
                return courses;
            })(),
        },
        series : [
            {
                name: '访问次数',
                type: 'pie',
                radius : '55%',
                center: ['50%', '60%'],
                data:(function(){
                    var courses = [];
                    $.ajax({
                        type:"GET",
                        url:"/stat?day=20170511",
                        dataType:'json',
                        async:false,
                        success:function(result) {
                            for(var i=0;i<result.length; i++){
                                courses.push({"value": result[i].value,"name":result[i].name});
                            }
                        }
                    })
                    return courses;
                })(),
                itemStyle: {
                    emphasis: {
                        shadowBlur: 10,
                        shadowOffsetX: 0,
                        shadowColor: 'rgba(0, 0, 0, 0.5)'
                    }
                }
            }
        ]
    };


    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option);
</script>
</body>
</html>

修改web.xml

 

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>stat</servlet-name>
    <servlet-class>com.liang.web.VideoAccessTopNServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>stat</servlet-name>
    <url-pattern>/stat</url-pattern>
  </servlet-mapping>
</web-app>

 整体项目结构:

 结果

 

 

 

 

2 使用Zeppelin

Zeppelin官网http://zeppelin.apache.org/

我的是在linux中:ip是我的linux的网址:下在本地一样

 

1)下载,解压,配置环境变量 
2)启动

zeppelin/bin/zeppelin-daemon.sh start
  • 1

3)打开web页面并配置使用

// 浏览器输入
localhost:8080

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值