在任务1进行的同时,使用侧边流,监控若发现order_status字段为退回完成, 将key设置成totalrefundordercount存入Redis中,value存放用户退款消费额。使用redis cli以get key方式获取totalrefundordercount值,将结果截图粘贴至客户端桌面【Release任务D提交结果.docx】中对应的任务序号下,需两次截图,第一次截图和第二次截图间隔1分钟以上,第一次截图放前面,第二次截图放后面;

使用Flink消费Kafka中的数据,统计商城实时订单实收金额(需要考虑订单状态,若有取消订单、申请退回、退回完成则不计入订单实收金额,其他状态的则累加),将key设置成totalprice存入Redis中。使用redis cli以get key方式获取totalprice值,将结果截图粘贴至客户端桌面【Release任务D提交结果.docx】中对应的任务序号下,需两次截图,第一次截图和第二次截图间隔1分钟以上,第一次截图放前面,第二次截图放后面;

--1.2. 获取Flink流运行环境
在flink工程目录的src/main/scala/org/example目录下新建TotalPrice.scala文件,新建object对象和main函数,在主方法中获取Flink流运行环境,代码如下:

package org.example

import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, WatermarkStrategy}
import org.apache.flink.connector.kafka.source.KafkaSource
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer
import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.connectors.redis.RedisSink
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig
import org.apache.flink.streaming.connectors.redis.common.mapper.{RedisCommand, RedisCommandDescription, RedisMapper}
import org.apache.flink.streaming.util.serialization.SimpleStringSchema
import org.apache.flink.table.api.bridge.scala.StreamTableEnvironment
import org.apache.flink.util.Collector

import java.text.SimpleDateFormat
import java.time.Duration

object TotalPrice {

  def main(args: Array[String]): Unit = {

    val env = StreamExecutionEnvironment.getExecutionEnvironment
    
  }

}

 
--1.3. 定义Kafka源
在main函数中定义kafka源,消费kafka的order主题,代码如下:

    val source = KafkaSource.builder[String].
      setBootstrapServers("localhost:9092").
      setTopics("order").
      setGroupId("order1").
      setStartingOffsets(OffsetsInitializer.earliest()).
      setValueOnlyDeserializer(new SimpleStringSchema()).build()
 
--1.4. 创建流
在main函数中使用kafka数据源创建flink流,代码如下:

    val stream = env.fromSource(source, WatermarkStrategy.noWatermarks[String], "Kafka Source")

 
--1.5. 定义订单数量统计侧边流标签
在main函数中定义订单金额统计侧边流标签,通过标签可以写出侧边流和获取侧边流,代码如下:

    val totalprice = OutputTag[(String, String)]("totalprice")
 
--1.6. 设置水位线
设置水位线
根据题目要求,允许数据延迟5s,那么我们就需要设置水位线超时时间为5s,代码如下:

    val newstream = stream.assignTimestampsAndWatermarks(WatermarkStrategy.forBoundedOutOfOrderness[String](Duration.ofSeconds(5))

 
--1.7. 设置事件时间
根据题目要求,计算中使用order_info或order_detail表中create_time或operate_time取两者中值较大者作为EventTime,所以我们需要自定义事件时间为create_time或operate_time较大的时间,代码(使用链式编程接1.6步骤的末尾)如下:

      .withTimestampAssigner(
        new SerializableTimestampAssigner[String] {
          override def extractTimestamp(t: String, l: Long):
          Long = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(if (t.split(",")(12) > t.split(",")(11)) t.split(",")(12) else t.split(",")(11)).getTime
        }
      ))
 
--1.8. 自定义处理函数
根据题目要求,统计商城实时订单金额(需要考虑订单状态,若有取消订单、申请退回、退回完成则不计入订单金额,其他状态则累加),需要在处理函数中将不是取消订单、申请退回、退回完成的订单输出到侧边流,主流中的数据不作处理,调用collector收集即可。码(使用链式编程接1.6步骤的末尾)如下:

  	.process(new ProcessFunction[String, String] {
      override def processElement(i: String, context: ProcessFunction[String, String]#Context, collector: Collector[String]): Unit = {
        println("getdataQ" + i)
        val orderstatus = i.split(",")(5)
        println("orderstatus" + orderstatus)
        val orderprice = i.split(",")(3)
        if (!orderstatus.equals("1003") && !orderstatus.equals("1005") && !orderstatus.equals("1006")) {
          context.output(totalprice, ("totalprice", orderprice))
        }
        collector.collect(i)
      }
    })
 
--1.9. 统计订单金额
获取侧边流,通过map转换根据key统计出订单的金额,代码如下:

    val newstream2 = newstream.getSideOutput(totalprice).map(t => ("totalprice", String.valueOf(t._2).toDouble)).keyBy(t => t._1).sum(1).map(t => {
      println("redisdata" + String.valueOf(t._2))
      String.valueOf(t._2)
    }
    )
 
--1.10. 创建redis连接池
根据题目要求,统计的结果需要保存到redis中,需要建立redis的连接池,代码如下:

    val conf = new FlinkJedisPoolConfig.Builder().setHost("localhost").build()
 
--1.11. 创建订单统计Redis映射器
由于订单的统计结果是在flink中,我们要把结果写入到redis,那么redis就需要知道数据是什么格式,key是什么,value是什么,redis执行的命令是什么,这就需要定义一个映射器来实现,代码如下:

    // 实现RedisMapper接口
    class MyRedisMapper2 extends RedisMapper[String] {
      override def getCommandDescription: RedisCommandDescription = new RedisCommandDescription(RedisCommand.SET, "totalprice")

      override def getKeyFromData(t: String): String = "totalprice"

      override def getValueFromData(t: String): String = t
    }
 
--1.12. 订单统计结果写入redis
通过侧边流设置redis的连接池信息和映射器信息就可以将数据保存到redis,代码如下:

    newstream2.addSink(new RedisSink[String](conf, new MyRedisMapper2))

 
--1.13. 启动任务
代码编写好以后,需要调用流运行环境来执行任务,代码如下:

    env.execute("Job")
 
--1.14. 打包代码
在Linux终端执行如下命令,使用maven打包代码

cd  /rgsoft/Desktop/Study/task/
/opt/apache-maven-3.9.1/bin/mvn clean
/opt/apache-maven-3.9.1/bin/mvn install

 
--1.15. 提交代码到集群
在Linux终端执行如下命令,提交jar包到集群

echo "classloader.check-leaked-classloader: false" >> /opt/flink/conf/flink-conf.yaml 
export HADOOP_CLASSPATH=`hadoop classpath`
/opt/flink/bin/flink run  -t yarn-per-job --detached  -c org.example.TotalPrice ./target/flink-1.0-SNAPSHOT.jar

 
--1.16. 查看任务结果
在redis终端查看写入的结果数据,执行如下命令连接redis,然后查看结果

# 连接redis
redis-cli
# 在redis命令行执行命令
keys *
get totalprice
quit

在任务1进行的同时,使用侧边流,监控若发现order_status字段为退回完成, 将key设置成totalrefundordercount存入Redis中,value存放用户退款消费额。使用redis cli以get key方式获取totalrefundordercount值,将结果截图粘贴至客户端桌面【Release任务D提交结果.docx】中对应的任务序号下,需两次截图,第一次截图和第二次截图间隔1分钟以上,第一次截图放前面,第二次截图放后面;

--2.2. 获取Flink流运行环境
在flink工程目录的src/main/scala/org/example目录下新建TotalRefundOrderCount.scala文件,新建object对象和main函数,在主方法中获取Flink流运行环境,代码如下:

package org.example

import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, WatermarkStrategy}
import org.apache.flink.connector.kafka.source.KafkaSource
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer
import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.connectors.redis.RedisSink
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig
import org.apache.flink.streaming.connectors.redis.common.mapper.{RedisCommand, RedisCommandDescription, RedisMapper}
import org.apache.flink.streaming.util.serialization.SimpleStringSchema
import org.apache.flink.table.api.bridge.scala.StreamTableEnvironment
import org.apache.flink.util.Collector

import java.text.SimpleDateFormat
import java.time.Duration

object TotalRefundOrderCount {

  def main(args: Array[String]): Unit = {

    val env = StreamExecutionEnvironment.getExecutionEnvironment
    
  }

}

 
--2.3. 定义Kafka源
在main函数中定义kafka源,消费kafka的order主题,代码如下:

val source = KafkaSource.builder[String].
      setBootstrapServers("localhost:9092").
      setTopics("order").
      setGroupId("order2").
      setStartingOffsets(OffsetsInitializer.earliest()).
      setValueOnlyDeserializer(new SimpleStringSchema()).build()
 
--2.4. 创建流
在main函数中使用kafka数据源创建flink流,代码如下:

    val stream = env.fromSource(source, WatermarkStrategy.noWatermarks[String], "Kafka Source")

 
--2.5. 定义侧边流标签
在main函数中定义订单金额统计侧边流标签和退单统计侧边流标签,通过标签可以写出侧边流和获取侧边流,代码如下:

    val totalprice=OutputTag[(String,String)]("totalprice")
    val totalrefundordercount=OutputTag[(String,String)]("totalrefundordercount")
 
--2.6. 设置水位线
根据题目要求,允许数据延迟5s,那么我们就需要设置水位线超时时间为5s,代码如下:

    val newstream= stream.assignTimestampsAndWatermarks(WatermarkStrategy.forBoundedOutOfOrderness[String](Duration.ofSeconds(5))

 
--2.7. 设置事件时间
根据题目要求,计算中使用order_info或order_detail表中create_time或operate_time取两者中值较大者作为EventTime,所以我们需要自定义事件时间为create_time或operate_time较大的时间,代码(使用链式编程接1.6步骤的末尾)如下:

      .withTimestampAssigner(
        new SerializableTimestampAssigner[String] {
          override def extractTimestamp(t: String, l: Long):
          Long = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(if (t.split(",")(12) > t.split(",")(11)) t.split(",")(12) else t.split(",")(11)).getTime
        }
      ))
 
--2.8. 自定义处理函数
根据题目要求,统计商城实时订单金额(需要考虑订单状态,若有取消订单、申请退回、退回完成则不计入订单金额,其他状态则累加),需要在处理函数中将不是取消订单、申请退回、退回完成的订单输出到侧边流,另外还需要将退回完成的订单输出到侧边流进行统计,主流中的数据不作处理,调用collector收集即可。码(使用链式编程接2.6步骤的末尾)如下:

  	.process(new ProcessFunction[String,String]{
      override def processElement(i: String, context: ProcessFunction[String, String]#Context, collector: Collector[String]): Unit ={
        println("getdataQ"+i)
        val orderstatus=i.split(",")(5)
        println("orderstatus"+orderstatus)
        val orderprice=i.split(",")(3)
        if(!orderstatus.equals("1003") && !orderstatus.equals("1005") && !orderstatus.equals("1006")){
          context.output(totalprice,("totalprice",orderprice))
        }
        if (orderstatus.equals("1006")) {
          context.output(totalrefundordercount, ("totalrefundordercount",orderprice))
        }
        collector.collect(i)
      }
    })
 
--2.9. 统计订单金额
获取侧边流,通过map转换根据key统计出订单的金额,代码如下:

    val newstream2=newstream.getSideOutput(totalprice).map(t=>("totalprice",String.valueOf(t._2).toDouble)).keyBy(t=>t._1).sum(1).map(t=>{
      println("redisdata" +String.valueOf(t._2))
      String.valueOf(t._2)}
    )
 
--2.10. 统计退回完成的订单的金额
获取侧边流,通过map转换根据key统计出订单的金额,代码如下:

    val newstream3=newstream.getSideOutput(totalrefundordercount).map(t=>("totalrefundordercount",String.valueOf(t._2).toDouble)).keyBy(t=>t._1).sum(1).map(t=>String.valueOf(t._2))

 
--2.11. 创建redis连接池
根据题目要求,统计的结果需要保存到redis中,需要建立redis的连接池,代码如下:

    val conf = new FlinkJedisPoolConfig.Builder().setHost("localhost").build()

 
--2.12. 创建订单统计Redis映射器
由于订单的统计结果是在flink中,我们要把结果写入到redis,那么redis就需要知道数据是什么格式,key是什么,value是什么,redis执行的命令是什么,这就需要定义一个映射器来实现,代码如下:

    // 实现RedisMapper接口
    class MyRedisMapper2 extends RedisMapper[String] {
      override def getCommandDescription: RedisCommandDescription = new RedisCommandDescription(RedisCommand.SET, "totalprice")

      override def getKeyFromData(t: String): String = "totalprice"

      override def getValueFromData(t: String): String = t
    }
 
--2.13. 创建退单统计Redis映射器
由于退单统计的结果是在flink中,我们要把结果写入到redis,那么redis就需要知道数据是什么格式,key是什么,value是什么,redis执行的命令是什么,这就需要定义一个映射器来实现,代码如下:

    // 实现RedisMapper接口
    class MyRedisMapper3 extends RedisMapper[String] {
      override def getCommandDescription: RedisCommandDescription = new RedisCommandDescription(RedisCommand.SET, "totalrefundordercount")

      override def getKeyFromData(t: String): String = "totalrefundordercount"

      override def getValueFromData(t: String): String = t
    }
 
--2.14. 订单统计结果写入redis
通过侧边流设置redis的连接池信息和映射器信息就可以将数据保存到redis,代码如下:

    newstream2.addSink(new RedisSink[String](conf, new MyRedisMapper2))

 
--2.15. 退单统计结果写入redis
通过侧边流设置redis的连接池信息和映射器信息就可以将数据保存到redis,代码如下:

    newstream3.addSink(new RedisSink[String](conf, new MyRedisMapper3))

 
--2.16. 启动任务
代码编写好以后,需要调用流运行环境来执行任务,代码如下:

    env.execute("Job")
 
--2.17. 打包代码
在Linux终端执行如下命令,使用maven打包代码

cd  /rgsoft/Desktop/Study/task/
/opt/apache-maven-3.9.1/bin/mvn clean
/opt/apache-maven-3.9.1/bin/mvn install

 
--2.18. 提交代码到集群
在Linux终端执行如下命令,提交jar包到集群

export HADOOP_CLASSPATH=`hadoop classpath`
/opt/flink/bin/flink run  -t yarn-per-job --detached  -c org.example.TotalRefundOrderCount ./target/flink-1.0-SNAPSHOT.jar

 
--2.19. 查看任务结果
在redis终端查看写入的结果数据,执行如下命令连接redis,然后查看结果

# 连接redis
redis-cli
# 在redis命令行执行命令
keys *
get totalrefundordercount
quit

 在任务1进行的同时,使用侧边流,监控若发现order_status字段为取消订单,将数据存入MySQL数据库shtd_result的order_info表中,然后在Linux的MySQL命令行中根据id降序排序,查询列id、consignee、consignee_tel、final_total_amount、feight_fee,查询出前5条,将SQL语句复制粘贴至客户端桌面【Release任务D提交结果.docx】中对应的任务序号下,将执行结果截图粘贴至客户端桌面【Release任务D提交结果.docx】中对应的任务序号下。

在flink工程目录的src/main/scala/org/example目录下新建CancelData_2_Mysql.scala文件,新建object对象和main函数,在主方法中获取Flink流运行环境,代码如下:

package org.example

import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, WatermarkStrategy}
import org.apache.flink.connector.jdbc.{JdbcConnectionOptions, JdbcExecutionOptions, JdbcSink, JdbcStatementBuilder}
import org.apache.flink.connector.kafka.source.KafkaSource
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer
import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.connectors.redis.RedisSink
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig
import org.apache.flink.streaming.connectors.redis.common.mapper.{RedisCommand, RedisCommandDescription, RedisMapper}
import org.apache.flink.streaming.util.serialization.SimpleStringSchema
import org.apache.flink.table.api.bridge.scala.StreamTableEnvironment
import org.apache.flink.util.Collector

import java.sql.PreparedStatement
import java.text.SimpleDateFormat
import java.time.Duration

object CancelData_2_Mysql {

  def main(args: Array[String]): Unit = {

    val env = StreamExecutionEnvironment.getExecutionEnvironment
    
  }

}

 
--3.3. 定义Kafka源
在main函数中定义kafka源,消费kafka的order主题,代码如下:

    val source = KafkaSource.builder[String].
      setBootstrapServers("localhost:9092").
      setTopics("order").
      setGroupId("order3").
      setStartingOffsets(OffsetsInitializer.earliest()).
      setValueOnlyDeserializer(new SimpleStringSchema()).build()
 
--3.4. 创建流
在main函数中使用kafka数据源创建flink流,代码如下:

val stream = env.fromSource(source, WatermarkStrategy.noWatermarks[String], "Kafka Source")
 
--3.5. 定义侧边流标签
在main函数中定义订单金额统计侧边流标签和取消订单侧边流标签,通过标签可以写出侧边流和获取侧边流,代码如下:

    //使用侧边流
    val totalprice = OutputTag[(String, String)]("totalprice")
    val canceldata = OutputTag[(String, String)]("canceldata")
 
--3.6. 设置水位线
根据题目要求,允许数据延迟5s,那么我们就需要设置水位线超时时间为5s,代码如下:

    val newstream = stream.assignTimestampsAndWatermarks(WatermarkStrategy.forBoundedOutOfOrderness[String](Duration.ofSeconds(5))

 
--3.7. 设置事件时间
根据题目要求,计算中使用order_info或order_detail表中create_time或operate_time取两者中值较大者作为EventTime,所以我们需要自定义事件时间为create_time或operate_time较大的时间,代码(使用链式编程接1.6步骤的末尾)如下:

      .withTimestampAssigner(
        new SerializableTimestampAssigner[String] {
          override def extractTimestamp(t: String, l: Long):
          Long = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(if (t.split(",")(12) > t.split(",")(11)) t.split(",")(12) else t.split(",")(11)).getTime
        }
      ))
 
--3.8. 自定义处理函数
根据题目要求,统计商城实时订单金额(需要考虑订单状态,若有取消订单、申请退回、退回完成则不计入订单数量,其他状态则累加),需要在处理函数中将不是取消订单、申请退回、退回完成的订单输出到侧边流,另外还需要将取消订单输出到侧边流,主流中的数据不作处理,调用collector收集即可。码(使用链式编程接3.6步骤的末尾)如下:

  	.process(new ProcessFunction[String, String] {
      override def processElement(i: String, context: ProcessFunction[String, String]#Context, collector: Collector[String]): Unit = {
        println("getdataQ" + i)
        val orderstatus = i.split(",")(5)
        println("orderstatus" + orderstatus)
        val orderprice = i.split(",")(3)
        if (!orderstatus.equals("1003") && !orderstatus.equals("1005") && !orderstatus.equals("1006")) {
          context.output(totalprice, ("totalprice", orderprice))
        }
        if (orderstatus.equals("1003")) {
          context.output(canceldata, ("canceldata", i))
        }
        collector.collect(i)
      }
    })
 
--3.9. 统计订单数量
获取侧边流,通过map转换根据key统计出订单的数量,代码如下:

    val newstream2 = newstream.getSideOutput(totalprice).map(t => ("totalprice", String.valueOf(t._2).toDouble)).keyBy(t => t._1).sum(1).map(t => {
      println("redisdata" + String.valueOf(t._2))
      String.valueOf(t._2)
    }
    )
 
--3.10. 取消订单的数据重新写回MySQL
获取侧边流,将数据写回MySQL,代码如下:

    val newstream4 = newstream.getSideOutput(canceldata).map(t => t._2)
    newstream4.addSink(JdbcSink.sink[String](
      "insert into shtd_result.order_info values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
      new JdbcStatementBuilder[String] {
        override def accept(t: PreparedStatement, u: String) = {
          val strarr = u.split(",")
          for (i <- 0 to strarr.size - 1) {
            if (i == 0) {
              t.setInt(i+1, String.valueOf(strarr(0)).toInt)
            } else {
              t.setString(i+1, String.valueOf(strarr(i)))
            }
          }
        }
      },
      JdbcExecutionOptions.builder()
        .withBatchIntervalMs(100)
        .withBatchSize(5)
        .withMaxRetries(0)
        .build(),
      new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
        .withUrl("jdbc:mysql://127.0.0.1:3306/shtd_result?useSSL=false&useUnicode=true&characterEncoding=utf8")
        .withDriverName("com.mysql.jdbc.Driver")
        .withUsername("root1")
        .withPassword("123456")
        .build()
    ))

 
--3.11. 创建redis连接池
根据题目要求,统计的结果需要保存到redis中,需要建立redis的连接池,代码如下:

    val conf = new FlinkJedisPoolConfig.Builder().setHost("localhost").build()
 
--3.12. 创建订单统计Redis映射器
由于订单的统计结果是在flink中,我们要把结果写入到redis,那么redis就需要知道数据是什么格式,key是什么,value是什么,redis执行的命令是什么,这就需要定义一个映射器来实现,代码如下:

    // 实现RedisMapper接口
    class MyRedisMapper2 extends RedisMapper[String] {
      override def getCommandDescription: RedisCommandDescription = new RedisCommandDescription(RedisCommand.SET, "totalprice")

      override def getKeyFromData(t: String): String = "totalprice"

      override def getValueFromData(t: String): String = t
    }
 
--3.13. 订单统计结果写入redis
通过侧边流设置redis的连接池信息和映射器信息就可以将数据保存到redis,代码如下:

    newstream2.addSink(new RedisSink[String](conf, new MyRedisMapper2))

 
--3.14. 启动任务
代码编写好以后,需要调用流运行环境来执行任务,代码如下:

    env.execute("Job")
 
--3.15. 打包代码
在Linux终端执行如下命令,使用maven打包代码

cd  /rgsoft/Desktop/Study/task/
/opt/apache-maven-3.9.1/bin/mvn clean
/opt/apache-maven-3.9.1/bin/mvn install

 
--3.16. 提交代码到集群
在Linux终端执行如下命令,提交jar包到集群

export HADOOP_CLASSPATH=`hadoop classpath`
/opt/flink/bin/flink run  -t yarn-per-job --detached  -c org.example.CancelData_2_Mysql ./target/flink-1.0-SNAPSHOT.jar

 
--3.17. 查看任务结果
在MySQL终端查看写入的结果数据,执行如下命令连接MySQL,然后查看结果

# 连接mysql
mysql -uroot -p123456
# 在mysql命令行执行命令
select id,consignee,consignee_tel,final_total_amount,feight_fee from shtd_result.order_info order by id desc limit 5;
exit