• Spark-2.3.2【SparkStreaming+SparkSQL-实时仪表盘应用】


     应用场景:实时仪表盘(即大屏),每个集团下有多个mall,每个mall下包含多家shop,需实时计算集团下各mall及其shop的实时销售分析(区域、业态、店铺TOP、总销售额等指标)并提供可视化展现,之前时候一直在Strom实现,现在改为Spark2.3.2实现。

    1、数据源:首先数据源来自于MQ、Socket、Flume和DFS等,一般Kafka、RocketMQ等居多,此处示例代码用的是RocketMQ;

    2、实时计算框架:Storm(实时计算,Spout发射Tuple到各个Bolt,来一条即处理一条,一百毫秒以内的延迟)、SparkStreaming(准实时计算,基于微批次的实时计算,即一定时间段内的micro batch,每个micro batch的结构为DStream,底层是RDD);

    3、此处Spark Streaming准实时处理应用流程:1、RocketMQ --> 2、SparkStreaming --> 3、SparkSQL(Parquet) --> 4、Redis(大屏使用) && HDFS(Hive数仓ODS层,ODS->DW[DWD-DWS]-DM);

    4、系统设计

    5、代码如下(4.3是Spark Streaming实现,复制粘贴即可运行):

    • 5.1、RocketMQConfig类(用于配置和构建MQ消费者)
    • 5.2、SparkStreaming自定义RocketMQ接收器(可靠的接收器)
    • 5.3、SparkStreaming销售分析实时计算
    • 5.4、账单实体类
    • 5.5、BCD即账单裁剪后的实体类(减少数据量传输即降低节点间的序列化和反序列化开销)
    • 5.6、SparkSQL销售分析业务实现类

    5.1、RocketMQConfig类(用于配置和构建MQ消费者)

    package com.mengyao.graph.etl.apps.commons.datasource.mq.receiver;
    
    import org.apache.commons.lang.Validate;
    import org.apache.rocketmq.client.ClientConfig;
    import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
    import org.apache.rocketmq.client.exception.MQClientException;
    import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
    import org.apache.rocketmq.remoting.common.RemotingUtil;
    
    import java.util.HashMap;
    import java.util.UUID;
    
    /**
     * RocketMQConfig for Consumer
     * @author mengyao
     * 
     */
    public class RocketMQConfig {
        
        // ------- the following is for common usage -------
        /**
         * RocketMq name server address
         */
        public static final String NAME_SERVER_ADDR = "nameserver.addr"; // Required
    
        public static final String CLIENT_NAME = "client.name";
    
        public static final String CLIENT_IP = "client.ip";
        public static final String DEFAULT_CLIENT_IP = RemotingUtil.getLocalAddress();
    
        public static final String CLIENT_CALLBACK_EXECUTOR_THREADS = "client.callback.executor.threads";
        public static final int DEFAULT_CLIENT_CALLBACK_EXECUTOR_THREADS = Runtime.getRuntime().availableProcessors();;
    
        public static final String NAME_SERVER_POLL_INTERVAL = "nameserver.poll.interval";
        public static final int DEFAULT_NAME_SERVER_POLL_INTERVAL = 30000; // 30 seconds
    
        public static final String BROKER_HEART_BEAT_INTERVAL = "brokerserver.heartbeat.interval";
        public static final int DEFAULT_BROKER_HEART_BEAT_INTERVAL = 30000; // 30 seconds
    
    
        // ------- the following is for push consumer mode -------
        /**
         * RocketMq consumer group
          */
        public static final String CONSUMER_GROUP = "consumer.group"; // Required
    
        /**
         * RocketMq consumer topic
         */
        public static final String CONSUMER_TOPIC = "consumer.topic"; // Required
    
        public static final String CONSUMER_TAG = "consumer.tag";
        public static final String DEFAULT_TAG = "*";
    
        public static final String CONSUMER_OFFSET_RESET_TO = "consumer.offset.reset.to";
        public static final String CONSUMER_OFFSET_LATEST = "latest";
        public static final String CONSUMER_OFFSET_EARLIEST = "earliest";
        public static final String CONSUMER_OFFSET_TIMESTAMP = "timestamp";
    
        public static final String CONSUMER_MESSAGES_ORDERLY = "consumer.messages.orderly";
    
        public static final String CONSUMER_OFFSET_PERSIST_INTERVAL = "consumer.offset.persist.interval";
        public static final int DEFAULT_CONSUMER_OFFSET_PERSIST_INTERVAL = 5000; // 5 seconds
    
        public static final String CONSUMER_MIN_THREADS = "consumer.min.threads";
        public static final int DEFAULT_CONSUMER_MIN_THREADS = 20;
    
        public static final String CONSUMER_MAX_THREADS = "consumer.max.threads";
        public static final int DEFAULT_CONSUMER_MAX_THREADS = 64;
    
    
        // ------- the following is for reliable Receiver -------
        public static final String QUEUE_SIZE = "spout.queue.size";
        public static final int DEFAULT_QUEUE_SIZE = 500;
    
        public static final String MESSAGES_MAX_RETRY = "spout.messages.max.retry";
        public static final int DEFAULT_MESSAGES_MAX_RETRY = 3;
    
        public static final String MESSAGES_TTL = "spout.messages.ttl";
        public static final int DEFAULT_MESSAGES_TTL = 300000;  // 5min
    
    
        // ------- the following is for pull consumer mode -------
    
        /**
         * Maximum rate (number of records per second) at which data will be read from each RocketMq partition ,
         * and the default value is "-1", it means consumer can pull message from rocketmq as fast as the consumer can.
         * Other that, you also enables or disables Spark Streaming's internal backpressure mechanism by the config
         *  "spark.streaming.backpressure.enabled".
         */
        public static final String  MAX_PULL_SPEED_PER_PARTITION = "pull.max.speed.per.partition";
    
        /**
         * To pick up the consume speed, the consumer can pull a batch of messages at a time. And the default
         * value is "32"
         */
        public static final String PULL_MAX_BATCH_SIZE = "pull.max.batch.size";
    
        /**
         * pull timeout for the consumer, and the default time is "3000".
         */
        public static final String PULL_TIMEOUT_MS = "pull.timeout.ms";
    
        // the following configs for consumer cache
        public static final String PULL_CONSUMER_CACHE_INIT_CAPACITY = "pull.consumer.cache.initialCapacity";
        public static final String PULL_CONSUMER_CACHE_MAX_CAPACITY = "pull.consumer.cache.maxCapacity";
        public static final String PULL_CONSUMER_CACHE_LOAD_FACTOR = "pull.consumer.cache.loadFactor";
    
    
        public static void buildConsumerConfigs(HashMap<String, String> props, DefaultMQPushConsumer consumer) {
            buildCommonConfigs(props, consumer);
    
            String group = props.get(CONSUMER_GROUP);
            Validate.notEmpty(group);
            consumer.setConsumerGroup(group);
    
            consumer.setPersistConsumerOffsetInterval(getInteger(props,
                    CONSUMER_OFFSET_PERSIST_INTERVAL, DEFAULT_CONSUMER_OFFSET_PERSIST_INTERVAL));
            consumer.setConsumeThreadMin(getInteger(props,
                    CONSUMER_MIN_THREADS, DEFAULT_CONSUMER_MIN_THREADS));
            consumer.setConsumeThreadMax(getInteger(props,
                    CONSUMER_MAX_THREADS, DEFAULT_CONSUMER_MAX_THREADS));
    
            String initOffset = props.get(CONSUMER_OFFSET_RESET_TO) != null ? props.get(CONSUMER_OFFSET_RESET_TO) : CONSUMER_OFFSET_LATEST;
            switch (initOffset) {
                case CONSUMER_OFFSET_EARLIEST:
                    consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
                    break;
                case CONSUMER_OFFSET_LATEST:
                    consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
                    break;
                case CONSUMER_OFFSET_TIMESTAMP:
                    consumer.setConsumeTimestamp(initOffset);
                    break;
                default:
                    consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
            }
    
            String topic = props.get(CONSUMER_TOPIC);
            Validate.notEmpty(topic);
            try {
                consumer.subscribe(topic, props.get(CONSUMER_TAG) != null ? props.get(CONSUMER_TAG) : DEFAULT_TAG);
            } catch (MQClientException e) {
                throw new IllegalArgumentException(e);
            }
        }
    
        public static void buildCommonConfigs(HashMap<String, String> props, ClientConfig client) {
            String namesvr = props.get(NAME_SERVER_ADDR);
            Validate.notEmpty(namesvr);
            client.setNamesrvAddr(namesvr);
    
            client.setClientIP(props.get(CLIENT_IP) != null ? props.get(CLIENT_IP) : DEFAULT_CLIENT_IP);
            // use UUID for client name by default
            String defaultClientName = UUID.randomUUID().toString();
            client.setInstanceName(props.get(CLIENT_NAME) != null ? props.get(CLIENT_NAME) : defaultClientName);
    
            client.setClientCallbackExecutorThreads(getInteger(props,
                    CLIENT_CALLBACK_EXECUTOR_THREADS, DEFAULT_CLIENT_CALLBACK_EXECUTOR_THREADS));
            client.setPollNameServerInterval(getInteger(props,
                    NAME_SERVER_POLL_INTERVAL, DEFAULT_NAME_SERVER_POLL_INTERVAL));
            client.setHeartbeatBrokerInterval(getInteger(props,
                    BROKER_HEART_BEAT_INTERVAL, DEFAULT_BROKER_HEART_BEAT_INTERVAL));
        }
    
        public static int getInteger(HashMap<String, String> props, String key, int defaultValue) {
            return Integer.parseInt(props.get(key) != null ? props.get(key) : String.valueOf(defaultValue));
        }
    
        public static boolean getBoolean(HashMap<String, String> props, String key, boolean defaultValue) {
            return Boolean.parseBoolean(props.get(key) != null ? props.get(key) : String.valueOf(defaultValue));
        }
    
    }

    4.2、SparkStreaming自定义RocketMQ接收器(可靠的接收器)

    package com.mengyao.graph.etl.apps.commons.datasource.mq.receiver;
    
    import org.apache.activemq.util.ByteArrayInputStream;
    import org.apache.commons.lang.StringUtils;
    import org.apache.commons.lang.Validate;
    //import org.apache.commons.lang3.SerializationUtils;
    import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
    import org.apache.rocketmq.client.consumer.MQPushConsumer;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
    import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
    import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
    import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
    import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
    import org.apache.rocketmq.client.exception.MQClientException;
    import org.apache.rocketmq.common.message.MessageExt;
    import org.apache.spark.storage.StorageLevel;
    import org.apache.spark.streaming.receiver.Receiver;
    
    import com.gooagoo.entity.mq.bill.MQBillMessage;
    import com.mengyao.graph.etl.apps.commons.beans.ods.fmt.BillInfoFmt;
    import com.mengyao.graph.etl.apps.commons.beans.ods.fmt.ConvertTool;
    import com.mengyao.graph.etl.apps.dashboard.beans.BCD;
    
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.util.HashMap;
    import java.util.List;
    
    /**
     * RocketMQ Receiver
     * @author mengyao
     *
     */
    public class RocketMQReceiver extends Receiver<BCD> {
        
        /**
         * 
         */
        private static final long serialVersionUID = 2274826339951693341L;
        private MQPushConsumer consumer;
        private boolean ordered;
        private HashMap<String, String> conf;
        
    
        public RocketMQReceiver(HashMap<String, String> conf, StorageLevel storageLevel) {
            super(storageLevel);
            this.conf = conf;
        }
    
        @Override
        public void onStart() {
            Validate.notEmpty(conf, "Consumer properties can not be empty");
            ordered = RocketMQConfig.getBoolean(conf, RocketMQConfig.CONSUMER_MESSAGES_ORDERLY, true);
            consumer = new DefaultMQPushConsumer();
            RocketMQConfig.buildConsumerConfigs(conf, (DefaultMQPushConsumer)consumer);
            if (ordered) {
                consumer.registerMessageListener(new MessageListenerOrderly() {
                    @Override
                    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                        if (process(msgs)) {
                            return ConsumeOrderlyStatus.SUCCESS;
                        } else {
                            return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
                        }
                    }
                });
            } else {
                consumer.registerMessageListener(new MessageListenerConcurrently() {
                    @Override
                    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                        if (process(msgs)) {
                            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                        } else {
                            return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                        }
                    }
                });
            }
            try {
                consumer.start();
                System.out.println("==== RocketMQReceiver start ====");
            } catch (MQClientException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    
        public boolean process(List<MessageExt> msgs) {
            if (msgs.isEmpty()) {
                System.out.println("==== Msgs is null! ====");
                return true;
            }
            System.out.println("==== receiver msgs: "+msgs.size()+" record. ====");
            try {
                for (MessageExt messageExt : msgs) {
                    //MQBillMessage message = SerializationUtils.deserialize(messageExt.getBody());
                    MQBillMessage message = deserialize(messageExt.getBody());
                    if (null != message) {
                        BillInfoFmt billFmt = ConvertTool.convertGagBillToOdsBillFmt(message.getData());
                        if (validBillType(billFmt)) {
                   /**
                    * this.store(BCD) 简单的接收一条存储一条,缺点是没有确认机制,不具备容错保证,会出现数据丢失。优点则是效率更高。
                    * this.store(Iterator<BCD>)阻塞调用,当接收到的记录都存储到Spark后才会确认成功,当接收方采用复制(默认存储级别为复制)则在复制完成后确认成功,但在缓冲中的数据不被保证,会被重新发送。具备容错保证,可确保数据0丢失。
                    */
    this.store(new BCD(billFmt.getId(), billFmt.getReceivableAmount(), billFmt.getSaleTime(), billFmt.getBillType(), billFmt.getShopId(), billFmt.getShopEntityId())); } billFmt=null; message.setData(null); message = null; } else { System.out.println("==== receiver msg is:"+messageExt.getBody()+", deserialize faild. ===="); } } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 验证账单类型是否为1、3、6 * @param bean * @return */ static boolean validBillType(BillInfoFmt bean) { if (null == bean) { System.out.println("==== BillBean is null! ===="); return false; } String billType = bean.getBillType(); if (StringUtils.isEmpty(billType)) { System.out.println("==== BillBean.billType is null! ===="); return false; } String billTypeTrim = billType.trim(); return billTypeTrim.equals("1") || billType.equals("3") || billType.equals("6"); } @Override public void onStop() { consumer.shutdown(); System.out.println("==== RocketMQReceiver stop ===="); } private static MQBillMessage deserialize(byte[] body) throws Exception { if (body == null) { throw new IllegalArgumentException("The byte array must not be null"); } MQBillMessage message = null; ObjectInputStream in = null; ByteArrayInputStream bais = null; try { bais = new ByteArrayInputStream(body); in = new ObjectInputStream(bais); message = (MQBillMessage) in.readObject(); } catch (final ClassNotFoundException ex) { ex.printStackTrace(); throw ex; } catch (final IOException ex) { ex.printStackTrace(); throw ex; } finally { try { if (bais != null) { bais.close(); } if (in != null) { in.close(); } } catch (final IOException ex) { // ignore close exception } } return message; } }

    4.3、SparkStreaming销售分析实时计

    package com.mengyao.graph.etl.apps.commons.datasource.bill;
    
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    import org.apache.commons.lang.Validate;
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.FileSystem;
    import org.apache.hadoop.fs.Path;
    import org.apache.spark.SparkConf;
    import org.apache.spark.api.java.JavaRDD;
    import org.apache.spark.api.java.JavaSparkContext;
    import org.apache.spark.api.java.StorageLevels;
    import org.apache.spark.api.java.function.Function0;
    import org.apache.spark.broadcast.Broadcast;
    import org.apache.spark.sql.Dataset;
    import org.apache.spark.sql.Row;
    import org.apache.spark.sql.SaveMode;
    import org.apache.spark.sql.SparkSession;
    import org.apache.spark.streaming.Durations;
    import org.apache.spark.streaming.api.java.JavaReceiverInputDStream;
    import org.apache.spark.streaming.api.java.JavaStreamingContext;
    import org.apache.spark.util.LongAccumulator;
    import org.apache.spark.util.SizeEstimator;
    import org.apache.spark.streaming.Time;
    
    import com.mengyao.graph.etl.apps.commons.beans.dim.RuianMall;
    import com.mengyao.graph.etl.apps.commons.datasource.mq.receiver.RocketMQConfig;
    import com.mengyao.graph.etl.apps.commons.datasource.mq.receiver.RocketMQReceiver;
    import com.mengyao.graph.etl.apps.dashboard.beans.BCD;
    import com.mengyao.graph.etl.apps.dashboard.service.SaleAnalysisService;
    
    /**
     * BillConsumer 大屏实时计算
     *         1、hdfs dfs -rm -r hdfs://bd001:8020/data/consumer/bill/checkpoint/*
     *         2、hdfs dfs -rm -r hdfs://bd001:8020/data/dashboard/ruian/sdt=curTime
     *         3、spark-submit --class com.mengyao.graph.etl.apps.commons.datasource.bill.BillConsumer --master yarn --deploy-mode cluster --driver-memory 4g --executor-cores 6 --executor-memory 5g --queue default --verbose data-graph-etl.jar
     * 
     * @author mengyao
     *
     */
    public class BillConsumer {
    
        private static final ThreadLocal<SimpleDateFormat> FORMATTER_YMD = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));
        private static final ThreadLocal<SimpleDateFormat> FORMATTER_YMDHMS = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmmss"));
        private static final ThreadLocal<SimpleDateFormat> FORMATTER_YMDHMSS = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmmssSSS"));
        private static final String BASE_PATH = "hdfs://bd001:8020/data/dashboard/ruian/";
        private static final String TMP_PATH = "/merge";
        private static String appName = "BillConsumer";
        private static String logLevel = "ERROR";
    
        
        public static void main(String[] args) {
            args = new String[] {"hdfs://bd001:8020/data/consumer/bill/checkpoint", "10", "base.mq.goo.com:9877", "Bill", "bill_dev_group_00013"};
            if (args.length < 5) {
                System.err.println("Usage: "+appName+" <checkpointDir> <milliseconds> <namesrvAddr> <groupId> <topic>");
                System.exit(1);
            }
            
            String checkPointDir = args[0];
            int second = Integer.parseInt(args[1]);
            String namesrv = args[2];
            Validate.notNull(namesrv, "RocketMQ namesrv not null!");
            String topic = args[3];
            Validate.notNull(topic, "RocketMQ topic not null!");
            String group = args[4];
            Validate.notNull(group, "RocketMQ group not null!");
            
            Function0<JavaStreamingContext> jscFunc = () -> createJavaSparkStreamingContext(checkPointDir, second, namesrv, topic, group);
            JavaStreamingContext jssc = JavaStreamingContext.getOrCreate(checkPointDir, jscFunc, new Configuration());
            jssc.sparkContext().setLogLevel(logLevel);
            
            try {
                jssc.start();
                jssc.awaitTermination();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                jssc.stop(false, true);//关闭StreamingContext但不关闭SparkContext,同时等待数据处理完成
            }
        }
        
        /**
         * 获取当前时间yyyyMMdd,匹配账单数据saleTime
         * @param curTime
         * @return
         */
        static String getCurrentDate(long curTime) {
            return FORMATTER_YMD.get().format(new Date(curTime));
        }
        
        /**
         * 打印bill RDD<BCD>的分区及占用空间大小,debug方法
         * @param rdd
         * @param time
         */
        static void printStream(JavaRDD<BCD> rdd, Time time) {rdd.id();
            System.out.println("==== time: "+FORMATTER_YMDHMSS.get().format(new Date(time.milliseconds()))+", rdd: partitions="+rdd.getNumPartitions()+", space="+SizeEstimator.estimate(rdd)/1048576+"mb");
        }
    
        /**
         * 合并parquet小文件
         * @param session
         * @param dfsDir
         */
        static void mergeSmallFiles(Dataset<Row> fulls, SparkSession session, String dfsDir) {
            fulls.coalesce(1).write().mode(SaveMode.Overwrite).parquet(BASE_PATH+TMP_PATH);
            session.read().parquet(BASE_PATH+TMP_PATH).coalesce(1).write().mode(SaveMode.Overwrite).parquet(dfsDir);
        }
        
        /**
         * 创建DFS Dir
         * @param session
         * @param dfsDir
         */
        static void mkDfsDir(SparkSession session, String dfsDir) {
            FileSystem fs = null;
            try {
                fs = FileSystem.get(session.sparkContext().hadoopConfiguration());
                Path path = new Path(dfsDir);
                if(!fs.exists(path)) {
                    fs.mkdirs(path);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (null != fs) {fs.close();}
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
        /**
         * 容错Driver
         * @param checkPointDir
         * @param second
         * @param namesrv
         * @param topic
         * @param group
         * @return
         */
        static JavaStreamingContext createJavaSparkStreamingContext(String checkPointDir, int second, String namesrv, String topic, String group) {
            try {
                SparkConf conf = new SparkConf()
                        //==== Enable Back Pressure
                        .set("spark.streaming.backpressure.enabled", "true")//启用被压机制
                        .set("spark.streaming.backpressure.initialRate", "50000")//初始接收数据条数,如该值为空时使用spark.streaming.backpressure.initialRate为默认值
                        .set("spark.streaming.receiver.maxRate", "100")//每秒接收器可接收的最大记录数
                        //==== Enable Dynamic Resource Allocation
                        .set("spark.dynamicAllocation.enabled", "false")//禁用spark动态资源分配
                        .set("spark.streaming.dynamicAllocation.enabled", "true")//启用SparkStreaming动态资源分配,该配置和spark动态资源分配存在冲突,只能使用一个
                        .set("spark.streaming.dynamicAllocation.minExecutors", "2")//启用SparkStreaming动态资源分配后的给应用使用的最小executor数
                        .set("spark.streaming.dynamicAllocation.maxExecutors", "3")//启用SparkStreaming动态资源分配后的给应用使用的最大executor数
                        //==== Spark Streaming Parallelism and WAL
                        .set("spark.streaming.concurrentJobs", "1")//并行job数量,默认1
                        .set("spark.streaming.blockInterval", "5000")//SparkStreaming接收器接收数据后5000毫秒生成block,默认200毫秒
                        .set("spark.streaming.receiver.writeAheadLog.enable", "true")//开启SparkStreaming接收器的WAL来确保接收器实现至少一次的容错语义
                        .set("spark.streaming.driver.writeAheadLog.allowBatching", "true")//driver端WAL
                        .set("spark.streaming.driver.writeAheadLog.batchingTimeout", "15000")
                        //==== Hive on Spark
                        .set("hive.execution.engine", "spark")//设置hive引擎为spark,hdp-2.6.1.0默认支持tez、mr,可通过应用级别配置使用Hive on Spark
                        .set("hive.enable.spark.execution.engine", "true")//启用Hive on Spark
                        .set("spark.driver.extraJavaOptions", "-Dhdp.version=2.6.1.0-129")//hdp-2.6.1.0中要求Hive on Spark必须指定driver的jvm参数
                        .set("spark.yarn.am.extraJavaOptions", "-Dhdp.version=2.6.1.0-129")//hdp-2.6.1.0中要求Hive on Spark必须指定ApplicationMaster的jvm参数
                        //==== Hive Merge Small Files
                        .set("hive.metastore.uris", "thrift://bd001:9083")//hive ThriftServer
                        .set("hive.merge.sparkfiles", "true")//合并spark小文件
                        .set("hive.merge.mapfiles", "true")//在只有map任务的作业结束时合并小文件。
                        .set("hive.merge.mapredfiles", "true")//在mapreduce作业结束时合并小文件。
                        .set("hive.merge.size.per.task", "268435456")//作业结束时合并文件的大小。
                        .set("hive.merge.smallfiles.avgsize", "100000")//当作业的平均输出文件大小小于此数量时,Hive将启动另一个map-reduce作业,以将输出文件合并为更大的文件。如果hive.merge.mapfiles为true,则仅对仅map作业执行此操作;对于hive.merge.mapredfiles为true,仅对map-reduce作业执行此操作。
                        //==== Spark SQL Optimizer
                        .set("spark.sql.warehouse.dir", "hdfs://bd001:8020/apps/hive/warehouse")//SparkSQL依赖的hive仓库地址
                        .set("spark.sql.files.maxPartitionBytes", "134217728")//SparkSQL读取文件数据时打包到一个分区的最大字节数
                        .set("spark.sql.files.openCostInBytes", "134217728")//当SparkSQL读取的文件中有大量小文件时,小于该值的文件将被合并处理,默认4M,此处设置为128M
                        .set("spark.sql.shuffle.partitions", "600")//SparkSQL运行shuffle的并行度
                        .set("spark.sql.autoBroadcastJoinThreshold", "67108864")//设置为64M,执行join时自动广播小于该值的表,默认10M
                        //==== Spark Core Configure
                        .set("spark.rdd.compress","true")//开启rdd压缩以节省内存
                        .set("spark.default.parallelism", "600")//并行任务数
                        .set("spark.rpc.askTimeout", "300")//spark rpc超时时间
                        .set("spark.eventLog.enabled", "true")//开启eventLog
                        //==== Application Configure
                        .set("spark.app.name", appName)//Spark Application名称
                        .set("spark.master", "yarn")//运行模式为Spark on YARN
                        .set("spark.deploy.mode", "cluster")//部署模式为yarn-cluster
                        .set("spark.driver.memory", "4g")//driver内存4g
                        .set("spark.driver.cores", "1")//driver计算vcore数量为1
                        .set("spark.executor.memory", "5g")//executor内存为4g
                        .set("spark.executor.heartbeatInterval", "20000")//executor心跳间隔20秒,默认10秒
                        .set("spark.yarn.archive", "hdfs://bd001:8020/hdp/apps/2.6.1.0-129/spark2")//spark依赖jar存档到hdfs指定位置
                        .set("spark.executor.extraJavaOptions", "-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseConcMarkSweepGC")//打印GC详情和耗时
                        .set("spark.jars", "/usr/hdp/2.6.1.0-129/sqoop/lib/mysql-connector-java.jar")//如果使用了数据库驱动,则通过此配置即可
                        //==== Serialized Configure
                        .set("spark.kryoserializer.buffer", "512k")//默认64k,设置为256k
                        .set("spark.kryoserializer.buffer.max", "256m")//默认64m,设置为256m
                        .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")//使用kryo序列化库
                        .registerKryoClasses(new Class[]{HashMap.class, BCD.class})
                        ;
                
                //构建MQ配置
                @SuppressWarnings("serial")
                HashMap<String, String> mqConf = new HashMap<String, String>() {{
                    put(RocketMQConfig.NAME_SERVER_ADDR, namesrv);
                    put(RocketMQConfig.CONSUMER_TOPIC, topic);
                    put(RocketMQConfig.CONSUMER_GROUP, group);
                }};
                
                JavaStreamingContext jssc = new JavaStreamingContext(conf, Durations.seconds(second));
                jssc.checkpoint(checkPointDir);
                jssc.remember(Durations.minutes(1440));
                
                //接収RocketMQ账单
                JavaReceiverInputDStream<BCD> billListRDD = jssc.receiverStream(new RocketMQReceiver(mqConf, StorageLevels.MEMORY_AND_DISK_SER));
                
                billListRDD
                    .foreachRDD((rdd, time) -> {
                        boolean isEmpty = rdd.partitions().isEmpty();
                        if (!isEmpty) {
                            printStream(rdd, time);
                            
                            long start = System.currentTimeMillis();
                            String curTime = getCurrentDate(start).intern();
                            String dfsDir = (BASE_PATH+"sdt="+curTime+"/").intern();
                            
                            //账单计数器
                            //LongAccumulator billAccumulator = BillAccumulator.getInstance(JavaSparkContext.fromSparkContext(rdd.context()), appName);
                            //billAccumulator.add(rdd.count());
                            
                            //初始化SparkSession
                            SparkSession session = HiveSession.getInstance(conf);
                            
                            //维表等广播
                            BroadcastDIM dim = BroadcastWrapper.getInstance(JavaSparkContext.fromSparkContext(rdd.context())).getValue();
                            SaleAnalysisService service = dim.getService();
                            Dataset<Row> shop = dim.getShop();
                            Dataset<Row> type = dim.getType();
                            Dataset<Row> ruian = dim.getRuian();
                            String mallIdStr = dim.getMallIdStr();
                            
                            Set<String> areaSet=dim.getAreaSet();
                            Map<String, Set<String>> areaMall=dim.getAreaMall();
                            Set<String> mallSet=dim.getMallSet();
                            Set<String> typeSet=dim.getTypeSet();
                            
                            
                            //如果时间为00:00:00时则认为是新的一天,更新广播数据
                            if ((curTime+"000000").equals(FORMATTER_YMDHMS.get().format(new Date(time.milliseconds())))) {
                                BroadcastWrapper.update(session, JavaSparkContext.fromSparkContext(rdd.context()));//更新dim表数据
                                mkDfsDir(session, dfsDir);//初始化dfsDir
                            }
                            
                            //先持久化本次接收到的账单(写入当日)
                            session.createDataFrame(rdd, BCD.class)
                                .filter("sdt = "+curTime)
                                .write()
                                .mode("append")
                                .parquet(dfsDir);
                            
                            //再读取全量账单(读取当日)
                            Dataset<Row> bills = session.read().parquet(dfsDir)
                                .filter("shopId in ("+mallIdStr+")")
                                .dropDuplicates("billId")
                                //.coalesce(1)
                                .cache();
                            
                            //计算大屏指标
                            System.out.println("==== 计算指标:时间条件:"+curTime+" ====");
                            service.totalSale(bills);
                            service.totalRefund(bills);
                            service.peakTime(bills);
                            service.areaSaleTrendForAll(bills, ruian,areaSet, curTime);
                            service.projectContrast(bills, ruian,areaMall);
                            service.saleForShopTop10(bills, shop, ruian);
                            service.projectTypeSaleContrast(bills, shop, type, ruian,areaSet,mallSet,typeSet);
                            long end = System.currentTimeMillis();
                            System.out.println("==== 计算指标:耗时:"+(end-start)+"/ms ====");
                            bills.unpersist();
                            //每天最多存6个文件
                            if (bills.inputFiles().length > 6) {
                                mergeSmallFiles(bills, session, dfsDir);
                            }
                        } else {//如果SparkStreaming接收的Batch为空,则不做处理
                            System.out.println("==== rdd is null! ");
                        }
                    });
                return jssc;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
    }
    
    /**
     * 广播DIM相关数据
     * @author mengyao
     *
     */
    class BroadcastWrapper {
        private static volatile Broadcast<BroadcastDIM> instance = null;
        
        public static Broadcast<BroadcastDIM> getInstance(JavaSparkContext jsc) {
            if (instance == null) {
                synchronized (BroadcastWrapper.class) {
                    if (instance == null) {
                        SparkSession session = HiveSession.getInstance(jsc.getConf());
                        BroadcastDIM dim = new BroadcastDIM(session);
                        dim.assign();
                        instance = jsc.broadcast(dim);
                    }
                }
            }
            return instance;
        }
        
        /**
         * 每日更新数据
         * @param batchTime    batch发生时间
         * @param dayES    每日开始时间
         */
        public static void update(SparkSession session, JavaSparkContext jsc) {
            BroadcastDIM dim = instance.getValue();
            if (null!=dim) {
                dim.assign();
                jsc.broadcast(dim);
            }
        }
    }
    
    class BroadcastDIM {
        private SparkSession session;
        private SaleAnalysisService service = new SaleAnalysisService();
        private Dataset<Row> shop;
        private Dataset<Row> type;
        private Dataset<Row> ruian;
        private String mallIdStr;
        private List<RuianMall> ruianList ;
        private Set<String>  areaSet;
        private Set<String>  typeSet;
        private Set<String> mallSet;
        private Map<String, Set<String>> areaMall;
        public BroadcastDIM(SparkSession session) {
            this.session = session;
        }
        public void assign() {
            ruian = session.sql("select id,item,name,area_en,area_cn,channel,mid,rmid from tbl_dim_ruian").cache();
            Row[] ruianRows = (Row[])ruian.collect();
            setRuianList(ruianRows);
            setAreaSet();
            setMallIdStr();
            setRuianMallAll();
            setMallSet();
            shop = session.sql("select id,shop_entity_id,shop_entity_name,shop_id,shop_entity_type_root,shop_entity_type_leaf,bill_match_mode,leasing_model,shop_entity_status,open_time,close_time,open_time_list,close_time_list,monite_begin_time_list,monite_end_time_list,marketing_copywriter,marketing_image,font_style,font_size,contract_area,province,city,area,billhint,is_del,brand,brand_code,classify,in_aera,storey,leasing_resource,shop_entity_source,create_time,c_time_stamp,shop_entity_img,business_area_id,source,status,logo,door_head_photo,business_license,certificate,coordinates,consume_per,brand_name,brand_log,alipay,process  "
                    + "from tbl_ods_shop where shop_id in ("+mallIdStr+")").cache();
            type = session.sql("select * from tbl_ods_type").cache();
            setRuianType();
        }
        public void  setRuianList(Row[] rows) {
            if(rows.length>0){
                ruianList=new ArrayList<>();
                for(Row row:rows){
                    RuianMall rm=new RuianMall();
                    if(!row.isNullAt(0)){//id
                        rm.setId(row.getInt(0));
                    }
                    if(!row.isNullAt(1)){//item
                        rm.setItem(row.getString(1));
                    }
                    if(!row.isNullAt(2)){//name
                        rm.setName(row.getString(2));
                    }
                    if(!row.isNullAt(3)){//area_en
                        rm.setAreaEn(row.getString(3));
                    }
                    if(!row.isNullAt(4)){//area_cn
                        rm.setArenCn(row.getString(4));
                    }
                    if(!row.isNullAt(5)){//channel
                        rm.setChannel(row.getString(5));
                    }
                    if(!row.isNullAt(6)){//mid
                        rm.setMid(row.getInt(6));
                    }
                    if(!row.isNullAt(7)){//rmid
                        rm.setRmid(row.getString(7));
                    }
                    ruianList.add(rm);
                }
            }
        }
        /**
         * 提取rmid 拼接成字符串
         * @param rows
         */
        public void setMallIdStr() {
            if(ruianList.size()>0){
                StringBuilder sbStr=new StringBuilder();
                for(RuianMall row : ruianList){
                    sbStr.append("'").append(row.getRmid()).append("',");
                }
                String tmpValue = sbStr.toString();
                mallIdStr=tmpValue.substring(0, tmpValue.length()-1);
            }
        }
        /**
         * 提取非空唯一中文区域名称 
         * @return
         */
        public void setAreaSet() {
            if(ruianList.size()>0){
                areaSet=new java.util.HashSet<>();
                for(RuianMall row:ruianList){
                    areaSet.add(row.getArenCn());
                }
            }
        }
        /**
         * 提取瑞安mall 14个机构中文名
         * 
         * @return
         * Map<String,Set<String>>
         * key:areaCn value :  Set<mallName>
         */
        public void setRuianMallAll( ){
            areaMall=new HashMap<>();
            ruianList.forEach(rm->{
                if(areaMall.containsKey(rm.getArenCn())){//存在,更新mall的列表
                    areaMall.get(rm.getArenCn()).add(rm.getName());
                }else{//新的区域,新的mall
                    Set<String> mallSet=new HashSet<>();
                    mallSet.add(rm.getName());
                    areaMall.put(rm.getArenCn(),mallSet);
                }
            });
        }
        /**
         * 提取瑞安mall 14个机构中文名
         * 
         * @return
         */
        public void setMallSet(){
            mallSet=new java.util.HashSet<>();
            ruianList.forEach(rm->{
                if(!mallSet.contains(rm.getName())){//存在,更新mall的列表
                    mallSet.add(rm.getName());
                }
            });
        }
        /**
         * 性能、性能、性能 考虑,先写死
         * 如果用账单、店铺、业态关联查询,效率会很慢
         * 
         * @return
         */
        public void setRuianType(){
            //TODO 目前写死,需要改
            typeSet=new HashSet<>();
            typeSet.add("服务");
            typeSet.add("零售");
            typeSet.add("娱乐");
            typeSet.add("主力店");
            typeSet.add("餐饮");
            typeSet.add("其它");
        }
        public SaleAnalysisService getService() {
            return service;
        }
        public void setService(SaleAnalysisService service) {
            this.service = service;
        }
        public Dataset<Row> getShop() {
            return shop;
        }
        public void setShop(Dataset<Row> shop) {
            this.shop = shop;
        }
        public Dataset<Row> getType() {
            return type;
        }
        public void setType(Dataset<Row> type) {
            this.type = type;
        }
        public Dataset<Row> getRuian() {
            return ruian;
        }
        public void setRuian(Dataset<Row> ruian) {
            this.ruian = ruian;
        }
        public String getMallIdStr() {
            return mallIdStr;
        }
        public void setMallIdStr(String mallIdStr) {
            this.mallIdStr = mallIdStr;
        }
        public List<RuianMall> getRuianList() {
            return ruianList;
        }
        public Set<String> getAreaSet() {
            return this.areaSet;
        }
        public Map<String, Set<String>> getAreaMall() {
            return this.areaMall;
        }
        public Set<String> getMallSet() {
            return this.mallSet;
        }
        public Set<String> getTypeSet() {
            return this.typeSet;
        }
    }
    
    /**
     * 账单累加器
     * @author mengyao
     *
     */
    class BillAccumulator {
        private static volatile LongAccumulator instance = null;
    
        public static LongAccumulator getInstance(JavaSparkContext jsc, String name) {
            if (instance == null) {
                synchronized (BillAccumulator.class) {
                    if (instance == null) {
                        instance = jsc.sc().longAccumulator(name);
                    }
                }
            }
            return instance;
        }
    }
    
    /**
     * SparkSQL的Hive数据源
     * @author mengyao
     *
     */
    class HiveSession {
        private static transient SparkSession instance = null;
        
        public static SparkSession getInstance(SparkConf conf) {
            if (instance == null) {
                instance = SparkSession.builder()
                        .config(conf)
                        .enableHiveSupport()
                        .getOrCreate();
            }
            return instance;
        }
    }

    4.4、账单实体类

    package com.mengyao.graph.etl.apps.commons.beans.ods.fmt;
    
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    import org.apache.commons.lang.StringUtils;
    
    import com.mengyao.graph.etl.apps.commons.beans.ods.BillInfo;
    import com.mengyao.graph.etl.apps.commons.beans.ods.DiscountDetailsInfo;
    import com.mengyao.graph.etl.apps.commons.beans.ods.GoodsDetailInfo;
    import com.mengyao.graph.etl.apps.commons.beans.ods.SettlementWayInfo;
    import com.mengyao.utils.DateTimeUtils;
    
    /**
     * 账单信息 gag_bill.bill_info
     * 
     * @author jmb
     * @update 2018-12-13 for mengyao
     */
    public class BillInfoFmt implements Serializable {
        private static final long serialVersionUID = 1L;
    
        /**
         * 预结单
         */
        public static final String BILLTYPE_JUJIEDAN = "2";
        /**
         * 结账单
         */
        public static final String BILLTYPE_JIEZHANGDAN = "1";
        /**
         * 日结账单
         */
        public static final String BILLTYPE_RIJIEDAN = "3";
        /**
         * 点菜单
         */
        public static final String BILLTYPE_DIANCAIDAN = "7";
    
        /**
         * 账单编号(系统产生),UUID
         */
        private String id;
    
        /**
         * 账单编号(系统产生),HBase主键
         */
        private String rowKey;
    
        /**
         * 商家编号(系统产生)
         */
        private String shopId;
    
        /**
         * 商家名称(系统产生)
         */
        private String shopName;
    
        /**
         * 实体店编号(系统产生)
         */
        private String shopEntityId;
    
        /**
         * 实体店名称(系统产生)
         */
        private String shopEntityName;
    
        /**
         * 创建时间(系统产生)
         */
        private String createTime;
    
        /**
         * 最后一次修改时间(系统产生)
         */
        private String cTimeStamp;
    
        /**
         * 信息上传时间(以header中创建时间为准yyyyMMddHHmmss)
         */
        private String hcTime;
    
        /**
         * 流水号(header中的流水号)
         */
        private String hserial;
    
        /**
         * 修正账单数据的设备编号(如果没有修正,则与原始上传账单的采集终端编号相同)
         */
        private String fixTerminal;
    
        /**
         * 采集终端编号(截获)
         */
        private String terminalNumber;
    
        /**
         * 账单文件名称,唯一(截获),12位MAC地址+17位时间
         */
        private String billfileName;
    
        /**
         * 反扫支付时终端生成的终端流水号UUID(全球唯一)[先支付后打单必填]
         */
        private String tsuuid;
    
        /**
         * 历史账单文件名称,记录合并过程中历次账单文件名称,全量,mongodb为Array
         */
        private List<String> billfileNameHis;
    
        /**
         * 账单序号,不保证唯一(截获)
         */
        private String billNo;
    
        /**
         * 截获时间(截获)
         */
        private String interceptTime;
    
        /**
         * 店铺全名(截获)
         */
        private String shopEntityFullName;
    
        /**
         * 店铺地址(截获)
         */
        private String shopEntityAddress;
    
        /**
         * 电话(截获)
         */
        private String telephone;
    
        /**
         * 售货员(截获)
         */
        private String saler;
    
        /**
         * 收银台(截获)
         */
        private String checkstand;
    
        /**
         * 收银员(截获)
         */
        private String cashier;
    
        /**
         * 应收金额(截获)
         */
        private Double receivableAmount;
    
        /**
         * 原始应收金额(截获)
         */
        private Double defaultReceivableAmount;
    
        /**
         * 商品数量(截获)
         */
        private Double totalNum;
    
        /**
         * 原始商品数量(截获)
         */
        private Double defaultTotalNum;
    
        /**
         * 小票流水号(截获)
         */
        private String billSerialNumber;
    
        /**
         * 总金额(截获)
         */
        private Double totalFee;
    
        /**
         * 原始总金额(截获)
         */
        private Double defaultTotalFee;
    
        /**
         * 实收金额(截获)
         */
        private Double paidAmount;
    
        /**
         * 原始实收金额(截获)
         */
        private Double defaultPaidAmount;
    
        /**
         * 折扣金额(截获)
         */
        private Double discountAmount;
    
        /**
         * 原始折扣金额(截获)
         */
        private Double defaultDiscountAmount;
    
        /**
         * 优惠金额(截获)
         */
        private Double couponAmount;
    
        /**
         * 原始优惠金额(截获)
         */
        private Double defaultCouponAmount;
    
        /**
         * 找零金额(截获)
         */
        private Double changeAmount;
    
        /**
         * 原始找零金额(截获)
         */
        private Double defaultChangeAmount;
    
        /**
         * 结算方式(支持多项)(截获)例:[{"a":5.0,"p":"现金"},{"a":10.01,"p":"书券"}],"a":结算金额,小数[截获,必填],
         * "p":"结算方式[截获,必填]"
         */
        private List<String> settlementWay;
    
        /**
         * 销售时间(截获)
         */
        private String saleTime;
    
        /**
         * 会员卡号(截获)
         */
        private String memberCardNumber;
    
        /**
         * 累计消费(截获)
         */
        private Double totalConsumption;
    
        /**
         * 原始累计消费(截获)
         */
        private Double defaultTotalConsumption;
    
        /**
         * 网址(截获)
         */
        private String website;
    
        /**
         * 小票图片(截获),只存url
         */
        private String billImage;
    
        /**
         * 商品详情(支持多项)(截获)[{"name":"可乐","itemserial":"PUMU00123","price":5.01,"totalnum":5.0,"totalprice":25.05},{"name":"金枪鱼","itemserial":"FOOD02012","price":10.55,"totalnum":1.5,"totalprice":15.83}]
         * ,"name":"商品名称[截获,选填]","itemserial":"条形码[截获,选填]","price":单价,小数[截获,选填],"totalnum":总数,小数[截获,选填],"totalprice":总价,小数[截获,选填]
         */
        private List<String> goodsDetails;
    
        /**
         * 房间号(截获)(酒店特有)
         */
        private String roomNo;
    
        /**
         * 入住姓名(截获)(酒店特有)
         */
        private String checkinName;
    
        /**
         * 桌号(截获)(餐饮特有)
         */
        private String deskNo;
    
        /**
         * 消费人数(截获)(一般用于餐饮)
         */
        private Double consumeNum;
    
        /**
         * 原始消费人数(截获)(一般用于餐饮)
         */
        private Double defaultConsumeNum;
    
        /**
         * 原始账单所有文本信息(截获)
         */
        private String billText;
    
        /**
         * 入住时间(截获)(酒店特有)
         */
        private String inTime;
    
        /**
         * 离店时间(截获)(钟点房特有)
         */
        private String outTime;
        /**
         * 默认打印时间
         */
        private String defaultPrintDate;
        /**
         * 默认入住时间
         */
        private String defaultInTime;
        /**
         * 默认离店时间
         */
        private String defaultOutTime;
    
        /**
         * 上传类型 1:全单上传 2. 筛选账单上传
         */
        private String uploadType;
    
        /**
         * 除了上面截获外的自定义数据(截获),json串(Map<String, Object>),json串中的key,value由采集终端自定义。
         */
        private Map<String, String> customRecord;
    
        /**
         * 账单类型1:结账单2:预结单 3:日结单 4:处方 5:预付押金单 6:退货单 7:点菜单 8:发票单 [选填]
         */
        private String billType;
    
        /**
         * 账单修改方式 0:默认值 1:收银员重打 2:人工修改金额 3:自动重解析
         */
        private String modifyType = "0";
    
        /**
         * 账单来源 1:设备采集 2:人工补录3、解析服务 4.第三方
         */
        private String billSource = "1";
    
        /**
         * 服务端解析路径[选填,用于服务端解析分析问题]
         */
        private String analyzPath;
    
        /**
         * 刷卡,钱包等匹配账单的key,格式BILL_MAC_金额_截获时间
         */
        private String billMatchKey;
    
        /**
         * 数据匹配支付结果等成功后,此字段保存支付结果等的主键 [选填] 匹配成功后为必填,理论上应该保证此值为全局唯一
         */
        private String matchId;
    
        /**
         * 默认销售时间;
         */
        private String defaultSaleTime;
    
        /**
         * 客户名称[截获,选填]
         */
        private String customerName;
        /**
         * 会员编号[截获,选填]
         */
        private String membershipId;
        /**
         * 会员级别[截获,选填]
         */
        private String memberLevels;
        /**
         * 宠物名称[截获,选填]
         */
        private String petName;
        /**
         * 宠物编号[截获,选填]
         */
        private String petNumber;
        /**
         * 负责人(医生)[截获,选填]
         */
        private String principal;
        /**
         * 预交押金[截获,选填]
         */
        private String deposit;
        /**
         * 打印时间yyyyMMddHHmmss[截获,选填,如果截获位数不够,后面补0]
         */
        private String printDate;
        /**
         * 默认拦截时间
         */
        private String defaultInterceptTime;
        /**
         * 匹配模型 sk0001:先刷卡后打单,单次刷卡; dd0001:先打单后刷卡,单次刷卡
         */
        private String printMatchType;
    
        /**
         * 账单合并时使用,唯一
         */
        private String billMergeKey;
        /**
         * 存重打单的应收金额
         */
        private Double modifyAmount;
        /**
         * 存重打单的应收金额List
         */
        private List<Double> modifyAmountList;
        /**
         * 存重打单的应收金额的销售时间List
         */
        private List<String> modifyAmountSaleTimeList;
        /**
         * 存重打单的应收金额的截获时间List
         */
        private List<String> modifyAmountInterceptTimeList;
        /**
         * 唯一标识[选填](目前可用来做积分标识使用)
         */
        private String uniqueId;
        /**
         * 历史唯一标识,记录合并过程中历次唯一标识,全量,mongodb为Array(目前可用来做积分标识使用)
         */
        private List<String> uniqueIdHis;
        /**
         * 是否追加二维码 1:是 2:否 [必填]
         */
        private String ifqrcode;
        /**
         * 优惠券券码
         */
        private String couponNum;
        /**
         * ERP会员
         */
        private String erpMemberCard;
        /**
         * 凭证类型 11:水单(商户pos、ERP打印)12:收银凭证(大pos打印) 13:支付凭证(签购单)99:类型未知 [选填,默认写99,表示未知]
         */
        private String voucherType;
        /**
         * 小票二维码[选填]
         */
        private String qrCode;
        /**
         * 积分标识 0:本次积分字段没找到或没有配置 1:有本次积分字段[选填]
         */
        private String integralmark;
        /**
         * 本次积分[选填]
         */
        private String thisintegral;
        /**
         * 备注[选填]
         */
        private String remarks;
        /**
         * 账单子类型,庖丁用于配置文件分类
         */
        private String templateName;
        /**
         * 购货方名称[截获,选填]
         */
        private String custName;
        /**
         * 购货方税号[截获,选填]
         */
        private String custTaxNo;
        /**
         * 购货方地址、电话[截获,选填]
         */
        private String custAdress;
        /**
         * 购货方银行及账号[截获,选填]
         */
        private String custBankAccount;
    
        /**
         * 第三方订单号(第三方系统唯一)
         */
        private String thirdPartyOrderNo;
        /**
         * 充值卡消费金额[截获,选填]
         */
        private Double rechargeableCardConsumeAmount;
        /**
         * 原始充值卡消费金额[截获,选填]
         */
        private Double defaultRechargeableCardConsumeAmount;
        /**
         * 实付金额[截获,选填]
         */
        private Double outOfPocketAmount;
        /**
         * 原始实付金额[截获,选填]
         */
        private Double defaultOutOfPocketAmount;
        /**
         * 充值金额[截获,选填]
         */
        private Double rechargeAmount;
        /**
         * 原始充值金额[截获,选填]
         */
        private Double defaultRechargeAmount;
        /**
         * 会员价[截获,选填]
         */
        private Double memberPrice;
        /**
         * 原始会员价[截获,选填]
         */
        private Double defaultMemberPrice;
        /**
         * 会员折扣率[截获,选填]
         */
        private Double memberDiscountrate;
        /**
         * 原始会员折扣率[截获,选填]
         */
        private Double defaultMemberDiscountrate;
        /**
         * 会员累计消费[截获,选填]
         */
        private Double memberTotalConsumption;
        /**
         * 原始会员累计消费[截获,选填]
         */
        private Double defaultMemberTotalConsumption;
        /**
         * 外卖单 1:外卖单 2:非外卖单[截获,选填]
         */
        private String takeout;
    
        /**
         * 优惠商品详情(支持多项)(截获),{"name":"可乐","price":5.01}
         */
        private List<String> discountDetails;
    
        public String getThirdPartyOrderNo() {
            return thirdPartyOrderNo;
        }
    
        public void setThirdPartyOrderNo(String thirdPartyOrderNo) {
            this.thirdPartyOrderNo = thirdPartyOrderNo;
        }
    
        public String getRemarks() {
            return remarks;
        }
    
        public void setRemarks(String remarks) {
            this.remarks = remarks;
        }
    
        public String getTemplateName() {
            return templateName;
        }
    
        public void setTemplateName(String templateName) {
            this.templateName = templateName;
        }
    
        public String getDefaultPrintDate() {
            return this.defaultPrintDate;
        }
    
        public void setDefaultPrintDate(String defaultPrintDate) {
            this.defaultPrintDate = defaultPrintDate;
        }
    
        public String getDefaultInTime() {
            return this.defaultInTime;
        }
    
        public void setDefaultInTime(String defaultInTime) {
            this.defaultInTime = defaultInTime;
        }
    
        public String getDefaultOutTime() {
            return this.defaultOutTime;
        }
    
        public void setDefaultOutTime(String defaultOutTime) {
            this.defaultOutTime = defaultOutTime;
        }
    
        public String getCouponNum() {
            return this.couponNum;
        }
    
        public void setCouponNum(String couponNum) {
            this.couponNum = couponNum;
        }
    
        public String getErpMemberCard() {
            return this.erpMemberCard;
        }
    
        public void setErpMemberCard(String erpMemberCard) {
            this.erpMemberCard = erpMemberCard;
        }
    
        public String getVoucherType() {
            return this.voucherType;
        }
    
        public void setVoucherType(String voucherType) {
            this.voucherType = voucherType;
        }
    
        public String getAnalyzPath() {
            return this.analyzPath;
        }
    
        public void setAnalyzPath(String analyzPath) {
            this.analyzPath = analyzPath;
        }
    
        public List<Double> getModifyAmountList() {
            return this.modifyAmountList;
        }
    
        public String getUniqueId() {
            return this.uniqueId;
        }
    
        public void setUniqueId(String uniqueId) {
            this.uniqueId = uniqueId;
        }
    
        public String getIfqrcode() {
            return this.ifqrcode;
        }
    
        public void setIfqrcode(String ifqrcode) {
            this.ifqrcode = ifqrcode;
        }
    
        public void setModifyAmountList(List<Double> modifyAmountList) {
            this.modifyAmountList = modifyAmountList;
        }
    
        public Double getModifyAmount() {
            return this.modifyAmount;
        }
    
        public void setModifyAmount(Double modifyAmount) {
            this.modifyAmount = modifyAmount;
        }
    
        public String getDefaultSaleTime() {
            return this.defaultSaleTime;
        }
    
        public void setDefaultSaleTime(String defaultSaleTime) {
            this.defaultSaleTime = defaultSaleTime;
        }
    
        public String getBillMergeKey() {
            return this.billMergeKey;
        }
    
        public void setBillMergeKey(String billMergeKey) {
            this.billMergeKey = billMergeKey;
        }
    
        public String getCustName() {
            return custName;
        }
    
        public void setCustName(String custName) {
            this.custName = custName;
        }
    
        public String getCustTaxNo() {
            return custTaxNo;
        }
    
        public void setCustTaxNo(String custTaxNo) {
            this.custTaxNo = custTaxNo;
        }
    
        public String getCustAdress() {
            return custAdress;
        }
    
        public void setCustAdress(String custAdress) {
            this.custAdress = custAdress;
        }
    
        public String getCustBankAccount() {
            return custBankAccount;
        }
    
        public void setCustBankAccount(String custBankAccount) {
            this.custBankAccount = custBankAccount;
        }
    
        public List<String> getUniqueIdHis() {
            return uniqueIdHis;
        }
    
        public void setUniqueIdHis(List<String> uniqueIdHis) {
            this.uniqueIdHis = uniqueIdHis;
        }
    
        public Double getRechargeableCardConsumeAmount() {
            return rechargeableCardConsumeAmount;
        }
    
        public void setRechargeableCardConsumeAmount(Double rechargeableCardConsumeAmount) {
            this.rechargeableCardConsumeAmount = rechargeableCardConsumeAmount;
        }
    
        public Double getDefaultRechargeableCardConsumeAmount() {
            return defaultRechargeableCardConsumeAmount;
        }
    
        public void setDefaultRechargeableCardConsumeAmount(Double defaultRechargeableCardConsumeAmount) {
            this.defaultRechargeableCardConsumeAmount = defaultRechargeableCardConsumeAmount;
        }
    
        public Double getOutOfPocketAmount() {
            return outOfPocketAmount;
        }
    
        public void setOutOfPocketAmount(Double outOfPocketAmount) {
            this.outOfPocketAmount = outOfPocketAmount;
        }
    
        public Double getDefaultOutOfPocketAmount() {
            return defaultOutOfPocketAmount;
        }
    
        public void setDefaultOutOfPocketAmount(Double defaultOutOfPocketAmount) {
            this.defaultOutOfPocketAmount = defaultOutOfPocketAmount;
        }
    
        public Double getRechargeAmount() {
            return rechargeAmount;
        }
    
        public void setRechargeAmount(Double rechargeAmount) {
            this.rechargeAmount = rechargeAmount;
        }
    
        public Double getDefaultRechargeAmount() {
            return defaultRechargeAmount;
        }
    
        public void setDefaultRechargeAmount(Double defaultRechargeAmount) {
            this.defaultRechargeAmount = defaultRechargeAmount;
        }
    
        public Double getMemberPrice() {
            return memberPrice;
        }
    
        public void setMemberPrice(Double memberPrice) {
            this.memberPrice = memberPrice;
        }
    
        public Double getDefaultMemberPrice() {
            return defaultMemberPrice;
        }
    
        public void setDefaultMemberPrice(Double defaultMemberPrice) {
            this.defaultMemberPrice = defaultMemberPrice;
        }
    
        public Double getMemberDiscountrate() {
            return memberDiscountrate;
        }
    
        public void setMemberDiscountrate(Double memberDiscountrate) {
            this.memberDiscountrate = memberDiscountrate;
        }
    
        public Double getDefaultMemberDiscountrate() {
            return defaultMemberDiscountrate;
        }
    
        public void setDefaultMemberDiscountrate(Double defaultMemberDiscountrate) {
            this.defaultMemberDiscountrate = defaultMemberDiscountrate;
        }
    
        public Double getMemberTotalConsumption() {
            return memberTotalConsumption;
        }
    
        public void setMemberTotalConsumption(Double memberTotalConsumption) {
            this.memberTotalConsumption = memberTotalConsumption;
        }
    
        public Double getDefaultMemberTotalConsumption() {
            return defaultMemberTotalConsumption;
        }
    
        public void setDefaultMemberTotalConsumption(Double defaultMemberTotalConsumption) {
            this.defaultMemberTotalConsumption = defaultMemberTotalConsumption;
        }
    
        public String getTakeout() {
            return takeout;
        }
    
        public void setTakeout(String takeout) {
            this.takeout = takeout;
        }
    
        public List<String> getDiscountDetails() {
            return discountDetails;
        }
    
        public void setDiscountDetails(List<String> discountDetails) {
            this.discountDetails = discountDetails;
        }
    
        public String getId() {
            return this.id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getShopId() {
            return this.shopId;
        }
    
        public void setShopId(String shopId) {
            this.shopId = shopId;
        }
    
        public String getShopName() {
            return this.shopName;
        }
    
        public void setShopName(String shopName) {
            this.shopName = shopName;
        }
    
        public String getShopEntityId() {
            return this.shopEntityId;
        }
    
        public void setShopEntityId(String shopEntityId) {
            this.shopEntityId = shopEntityId;
        }
    
        public String getShopEntityName() {
            return this.shopEntityName;
        }
    
        public void setShopEntityName(String shopEntityName) {
            this.shopEntityName = shopEntityName;
        }
    
        public String getCreateTime() {
            return this.createTime;
        }
    
        public void setCreateTime(String createTime) {
            this.createTime = createTime;
        }
    
        public String getCTimeStamp() {
            return this.cTimeStamp;
        }
    
        public void setCTimeStamp(String cTimeStamp) {
            this.cTimeStamp = cTimeStamp;
        }
    
        public String getHcTime() {
            return this.hcTime;
        }
    
        public void setHcTime(String hcTime) {
            this.hcTime = hcTime;
        }
    
        public String getHserial() {
            return this.hserial;
        }
    
        public void setHserial(String hserial) {
            this.hserial = hserial;
        }
    
        public String getFixTerminal() {
            return this.fixTerminal;
        }
    
        public void setFixTerminal(String fixTerminal) {
            this.fixTerminal = fixTerminal;
        }
    
        public String getTerminalNumber() {
            return this.terminalNumber;
        }
    
        public void setTerminalNumber(String terminalNumber) {
            this.terminalNumber = terminalNumber;
        }
    
        public String getBillfileName() {
            return this.billfileName;
        }
    
        public void setBillfileName(String billfileName) {
            this.billfileName = billfileName;
        }
    
        public List<String> getBillfileNameHis() {
            return this.billfileNameHis;
        }
    
        public void setBillfileNameHis(List<String> billfileNameHis) {
            this.billfileNameHis = billfileNameHis;
        }
    
        public String getBillNo() {
            return this.billNo;
        }
    
        public void setBillNo(String billNo) {
            this.billNo = billNo;
        }
    
        public String getInterceptTime() {
            return this.interceptTime;
        }
    
        public void setInterceptTime(String interceptTime) {
            this.interceptTime = interceptTime;
        }
    
        public String getShopEntityFullName() {
            return this.shopEntityFullName;
        }
    
        public void setShopEntityFullName(String shopEntityFullName) {
            this.shopEntityFullName = shopEntityFullName;
        }
    
        public String getShopEntityAddress() {
            return this.shopEntityAddress;
        }
    
        public void setShopEntityAddress(String shopEntityAddress) {
            this.shopEntityAddress = shopEntityAddress;
        }
    
        public String getTelephone() {
            return this.telephone;
        }
    
        public void setTelephone(String telephone) {
            this.telephone = telephone;
        }
    
        public String getSaler() {
            return this.saler;
        }
    
        public void setSaler(String saler) {
            this.saler = saler;
        }
    
        public String getCheckstand() {
            return this.checkstand;
        }
    
        public void setCheckstand(String checkstand) {
            this.checkstand = checkstand;
        }
    
        public String getCashier() {
            return this.cashier;
        }
    
        public void setCashier(String cashier) {
            this.cashier = cashier;
        }
    
        public Double getReceivableAmount() {
            return this.receivableAmount;
        }
    
        public void setReceivableAmount(Double receivableAmount) {
            this.receivableAmount = receivableAmount;
        }
    
        public Double getTotalNum() {
            return this.totalNum;
        }
    
        public void setTotalNum(Double totalNum) {
            this.totalNum = totalNum;
        }
    
        public String getBillSerialNumber() {
            return this.billSerialNumber;
        }
    
        public void setBillSerialNumber(String billSerialNumber) {
            this.billSerialNumber = billSerialNumber;
        }
    
        public Double getTotalFee() {
            return this.totalFee;
        }
    
        public void setTotalFee(Double totalFee) {
            this.totalFee = totalFee;
        }
    
        public Double getPaidAmount() {
            return this.paidAmount;
        }
    
        public void setPaidAmount(Double paidAmount) {
            this.paidAmount = paidAmount;
        }
    
        public Double getDiscountAmount() {
            return this.discountAmount;
        }
    
        public void setDiscountAmount(Double discountAmount) {
            this.discountAmount = discountAmount;
        }
    
        public Double getCouponAmount() {
            return this.couponAmount;
        }
    
        public void setCouponAmount(Double couponAmount) {
            this.couponAmount = couponAmount;
        }
    
        public Double getChangeAmount() {
            return this.changeAmount;
        }
    
        public void setChangeAmount(Double changeAmount) {
            this.changeAmount = changeAmount;
        }
    
        public List<String> getSettlementWay() {
            return this.settlementWay;
        }
    
        public void setSettlementWay(List<String> settlementWay) {
            this.settlementWay = settlementWay;
        }
    
        public String getSaleTime() {
            return saleTime;
        }
    
        public void setSaleTime(String saleTime) {
            this.saleTime = saleTime;
        }
    
        public String getMemberCardNumber() {
            return this.memberCardNumber;
        }
    
        public void setMemberCardNumber(String memberCardNumber) {
            this.memberCardNumber = memberCardNumber;
        }
    
        public Double getTotalConsumption() {
            return this.totalConsumption;
        }
    
        public void setTotalConsumption(Double totalConsumption) {
            this.totalConsumption = totalConsumption;
        }
    
        public String getWebsite() {
            return this.website;
        }
    
        public void setWebsite(String website) {
            this.website = website;
        }
    
        public String getBillImage() {
            return this.billImage;
        }
    
        public void setBillImage(String billImage) {
            this.billImage = billImage;
        }
    
        public List<String> getGoodsDetails() {
            return this.goodsDetails;
        }
    
        public void setGoodsDetails(List<String> goodsDetails) {
            this.goodsDetails = goodsDetails;
        }
    
        public String getRoomNo() {
            return this.roomNo;
        }
    
        public void setRoomNo(String roomNo) {
            this.roomNo = roomNo;
        }
    
        public String getCheckinName() {
            return this.checkinName;
        }
    
        public void setCheckinName(String checkinName) {
            this.checkinName = checkinName;
        }
    
        public String getDeskNo() {
            return this.deskNo;
        }
    
        public void setDeskNo(String deskNo) {
            this.deskNo = deskNo;
        }
    
        public Double getConsumeNum() {
            return this.consumeNum;
        }
    
        public void setConsumeNum(Double consumeNum) {
            this.consumeNum = consumeNum;
        }
    
        public String getBillText() {
            return this.billText;
        }
    
        public void setBillText(String billText) {
             this.billText = billText;
        }
    
        public Map<String, String> getCustomRecord() {
            return this.customRecord;
        }
    
        public void setCustomRecord(Map<String, String> customRecord) {
            this.customRecord = customRecord;
        }
    
        public String getBillType() {
            return this.billType;
        }
    
        public void setBillType(String billType) {
            this.billType = billType;
        }
    
        public String getInTime() {
            return this.inTime;
        }
    
        public String getOutTime() {
            return this.outTime;
        }
    
        public void setInTime(String inTime) {
            this.inTime = inTime;
        }
    
        public void setOutTime(String outTime) {
            this.outTime = outTime;
        }
    
        public String getBillMatchKey() {
            return this.billMatchKey;
        }
    
        public void setBillMatchKey(String billMatchKey) {
            this.billMatchKey = billMatchKey;
        }
    
        public String getMatchId() {
            return this.matchId;
        }
    
        public void setMatchId(String matchId) {
            this.matchId = matchId;
        }
    
        public String getPrintMatchType() {
            return this.printMatchType;
        }
    
        public void setPrintMatchType(String printMatchType) {
            this.printMatchType = printMatchType;
        }
    
        public String getTsuuid() {
            return this.tsuuid;
        }
    
        public void setTsuuid(String tsuuid) {
            this.tsuuid = tsuuid;
        }
    
        public String getUploadType() {
            return this.uploadType;
        }
    
        public void setUploadType(String uploadType) {
            this.uploadType = uploadType;
        }
    
        public String getCustomerName() {
            return this.customerName;
        }
    
        public void setCustomerName(String customerName) {
            this.customerName = customerName;
        }
    
        public String getMembershipId() {
            return this.membershipId;
        }
    
        public void setMembershipId(String membershipId) {
            this.membershipId = membershipId;
        }
    
        public String getMemberLevels() {
            return this.memberLevels;
        }
    
        public void setMemberLevels(String memberLevels) {
            this.memberLevels = memberLevels;
        }
    
        public String getPetName() {
            return this.petName;
        }
    
        public void setPetName(String petName) {
            this.petName = petName;
        }
    
        public String getPetNumber() {
            return this.petNumber;
        }
    
        public void setPetNumber(String petNumber) {
            this.petNumber = petNumber;
        }
    
        public String getPrincipal() {
            return this.principal;
        }
    
        public void setPrincipal(String principal) {
            this.principal = principal;
        }
    
        public String getDeposit() {
            return this.deposit;
        }
    
        public void setDeposit(String deposit) {
            this.deposit = deposit;
        }
    
        public String getPrintDate() {
            return this.printDate;
        }
    
        public void setPrintDate(String printDate) {
            this.printDate = printDate;
        }
    
        public String getDefaultInterceptTime() {
            return this.defaultInterceptTime;
        }
    
        public void setDefaultInterceptTime(String defaultInterceptTime) {
            this.defaultInterceptTime = defaultInterceptTime;
        }
    
        public String getModifyType() {
            return this.modifyType;
        }
    
        public void setModifyType(String modifyType) {
            this.modifyType = modifyType;
        }
    
        public String getBillSource() {
            return this.billSource;
        }
    
        public void setBillSource(String billSource) {
            this.billSource = billSource;
        }
    
        public String getQrCode() {
            return qrCode;
        }
    
        public void setQrCode(String qrCode) {
            this.qrCode = qrCode;
        }
    
        public String getIntegralmark() {
            return integralmark;
        }
    
        public void setIntegralmark(String integralmark) {
            this.integralmark = integralmark;
        }
    
        public String getThisintegral() {
            return thisintegral;
        }
    
        public void setThisintegral(String thisintegral) {
            this.thisintegral = thisintegral;
        }
    
        public List<String> getModifyAmountSaleTimeList() {
            return modifyAmountSaleTimeList;
        }
    
        public void setModifyAmountSaleTimeList(List<String> modifyAmountSaleTimeList) {
            this.modifyAmountSaleTimeList = modifyAmountSaleTimeList;
        }
    
        public List<String> getModifyAmountInterceptTimeList() {
            return modifyAmountInterceptTimeList;
        }
    
        public void setModifyAmountInterceptTimeList(List<String> modifyAmountInterceptTimeList) {
            this.modifyAmountInterceptTimeList = modifyAmountInterceptTimeList;
        }
    
        public Double getDefaultReceivableAmount() {
            return defaultReceivableAmount;
        }
    
        public void setDefaultReceivableAmount(Double defaultReceivableAmount) {
            this.defaultReceivableAmount = defaultReceivableAmount;
        }
    
        public Double getDefaultTotalNum() {
            return defaultTotalNum;
        }
    
        public void setDefaultTotalNum(Double defaultTotalNum) {
            this.defaultTotalNum = defaultTotalNum;
        }
    
        public Double getDefaultTotalFee() {
            return defaultTotalFee;
        }
    
        public void setDefaultTotalFee(Double defaultTotalFee) {
            this.defaultTotalFee = defaultTotalFee;
        }
    
        public Double getDefaultPaidAmount() {
            return defaultPaidAmount;
        }
    
        public void setDefaultPaidAmount(Double defaultPaidAmount) {
            this.defaultPaidAmount = defaultPaidAmount;
        }
    
        public Double getDefaultDiscountAmount() {
            return defaultDiscountAmount;
        }
    
        public void setDefaultDiscountAmount(Double defaultDiscountAmount) {
            this.defaultDiscountAmount = defaultDiscountAmount;
        }
    
        public Double getDefaultCouponAmount() {
            return defaultCouponAmount;
        }
    
        public void setDefaultCouponAmount(Double defaultCouponAmount) {
            this.defaultCouponAmount = defaultCouponAmount;
        }
    
        public Double getDefaultChangeAmount() {
            return defaultChangeAmount;
        }
    
        public void setDefaultChangeAmount(Double defaultChangeAmount) {
            this.defaultChangeAmount = defaultChangeAmount;
        }
    
        public Double getDefaultTotalConsumption() {
            return defaultTotalConsumption;
        }
    
        public void setDefaultTotalConsumption(Double defaultTotalConsumption) {
            this.defaultTotalConsumption = defaultTotalConsumption;
        }
    
        public Double getDefaultConsumeNum() {
            return defaultConsumeNum;
        }
    
        public void setDefaultConsumeNum(Double defaultConsumeNum) {
            this.defaultConsumeNum = defaultConsumeNum;
        }
    
        public String getRowKey() {
            return rowKey;
        }
    
        public void setRowKey(String rowKey) {
            this.rowKey = rowKey;
        }
    
        @Override
        public String toString() {
            return id + "	" + rowKey + "	" + shopId + "	" + shopName + "	" + shopEntityId + "	" + shopEntityName + "	"
                    + createTime + "	" + cTimeStamp + "	" + hcTime + "	" + hserial + "	" + fixTerminal + "	"
                    + terminalNumber + "	" + billfileName + "	" + tsuuid + "	" + billfileNameHis + "	" + billNo + "	"
                    + interceptTime + "	" + shopEntityFullName + "	" + shopEntityAddress + "	" + telephone + "	" + saler
                    + "	" + checkstand + "	" + cashier + "	" + receivableAmount + "	" + defaultReceivableAmount + "	"
                    + totalNum + "	" + defaultTotalNum + "	" + billSerialNumber + "	" + totalFee + "	" + defaultTotalFee
                    + "	" + paidAmount + "	" + defaultPaidAmount + "	" + discountAmount + "	" + defaultDiscountAmount
                    + "	" + couponAmount + "	" + defaultCouponAmount + "	" + changeAmount + "	" + defaultChangeAmount
                    + "	" + settlementWay + "	" + saleTime + "	" + memberCardNumber + "	" + totalConsumption + "	"
                    + defaultTotalConsumption + "	" + website + "	" + billImage + "	" + goodsDetails + "	" + roomNo
                    + "	" + checkinName + "	" + deskNo + "	" + consumeNum + "	" + defaultConsumeNum + "	" + billText
                    + "	" + inTime + "	" + outTime + "	" + defaultPrintDate + "	" + defaultInTime + "	"
                    + defaultOutTime + "	" + uploadType + "	" + customRecord + "	" + billType + "	" + modifyType + "	"
                    + billSource + "	" + analyzPath + "	" + billMatchKey + "	" + matchId + "	" + defaultSaleTime + "	"
                    + customerName + "	" + membershipId + "	" + memberLevels + "	" + petName + "	" + petNumber + "	"
                    + principal + "	" + deposit + "	" + printDate + "	" + defaultInterceptTime + "	" + printMatchType
                    + "	" + billMergeKey + "	" + modifyAmount + "	" + modifyAmountList + "	" + modifyAmountSaleTimeList
                    + "	" + modifyAmountInterceptTimeList + "	" + uniqueId + "	" + uniqueIdHis + "	" + ifqrcode + "	"
                    + couponNum + "	" + erpMemberCard + "	" + voucherType + "	" + qrCode + "	" + integralmark + "	"
                    + thisintegral + "	" + remarks + "	" + templateName + "	" + custName + "	" + custTaxNo + "	"
                    + custAdress + "	" + custBankAccount + "	" + thirdPartyOrderNo + "	" + rechargeableCardConsumeAmount
                    + "	" + defaultRechargeableCardConsumeAmount + "	" + outOfPocketAmount + "	"
                    + defaultOutOfPocketAmount + "	" + rechargeAmount + "	" + defaultRechargeAmount + "	" + memberPrice
                    + "	" + defaultMemberPrice + "	" + memberDiscountrate + "	" + defaultMemberDiscountrate + "	"
                    + memberTotalConsumption + "	" + defaultMemberTotalConsumption + "	" + takeout + "	"
                    + discountDetails;
        }
    
        public static BillInfoFmt cloneBill(BillInfo fromBean) {
            if (null == fromBean) {
                return null;
            }
            BillInfoFmt toBean = new BillInfoFmt();
            toBean.setId(stringRemove(fromBean.getId()));
            toBean.setRowKey(stringRemove(fromBean.getRowKey()));
            toBean.setShopId(stringRemove(fromBean.getShopId()));
            toBean.setShopName(stringRemove(fromBean.getShopName()));
            toBean.setShopEntityId(stringRemove(fromBean.getShopEntityId()));
            toBean.setShopEntityName(stringRemove(fromBean.getShopEntityName()));
            toBean.setCreateTime(DateTimeUtils.getYmdhmsForNo(fromBean.getCreateTime()));
            toBean.setCTimeStamp(DateTimeUtils.getYmdhmsForNo(fromBean.getCTimeStamp()));
            toBean.setHcTime(stringRemove(fromBean.getHcTime()));
            toBean.setHserial(stringRemove(fromBean.getHserial()));
            toBean.setFixTerminal(stringRemove(fromBean.getFixTerminal()));
            toBean.setTerminalNumber(stringRemove(fromBean.getTerminalNumber()));
            toBean.setBillfileName(stringRemove(fromBean.getBillfileName()));
            toBean.setTsuuid(stringRemove(fromBean.getTsuuid()));
            toBean.setBillfileNameHis(fromBean.getBillfileNameHis());
            toBean.setBillNo(stringRemove(fromBean.getBillNo()));
            toBean.setInterceptTime(DateTimeUtils.getYmdhmsForNo(fromBean.getInterceptTime()));
            toBean.setShopEntityFullName(stringRemove(fromBean.getShopEntityFullName()));
            toBean.setShopEntityAddress(stringRemove(fromBean.getShopEntityAddress()));
            toBean.setTelephone(stringRemove(fromBean.getTelephone()));
            toBean.setSaler(stringRemove(fromBean.getSaler()));
            toBean.setCheckstand(stringRemove(fromBean.getCheckstand()));
            toBean.setCashier(stringRemove(fromBean.getCashier()));
            toBean.setReceivableAmount(fromBean.getReceivableAmount());
            toBean.setDefaultReceivableAmount(fromBean.getDefaultReceivableAmount());
            toBean.setTotalNum(fromBean.getTotalNum());
            toBean.setDefaultTotalNum(fromBean.getDefaultTotalNum());
            toBean.setBillSerialNumber(stringRemove(fromBean.getBillSerialNumber()));
            toBean.setTotalFee(fromBean.getTotalFee());
            toBean.setDefaultTotalFee(fromBean.getDefaultTotalFee());
            toBean.setPaidAmount(fromBean.getPaidAmount());
            toBean.setDefaultPaidAmount(fromBean.getDefaultPaidAmount());
            toBean.setDiscountAmount(fromBean.getDiscountAmount());
            toBean.setDefaultDiscountAmount(fromBean.getDefaultDiscountAmount());
            toBean.setCouponAmount(fromBean.getCouponAmount());
            toBean.setDefaultCouponAmount(fromBean.getDefaultCouponAmount());
            toBean.setChangeAmount(fromBean.getChangeAmount());
            toBean.setDefaultChangeAmount(fromBean.getDefaultChangeAmount());
            if (null != fromBean.getSettlementWay()) {
                List<SettlementWayInfo> rawList = fromBean.getSettlementWay();
                List<String> list = new ArrayList<>();
                for (SettlementWayInfo raw : rawList) {
                    list.add(raw.toString());
                }
                toBean.setSettlementWay(list);
            }
            toBean.setSaleTime(fromBean.getSaleTime());
            toBean.setMemberCardNumber(stringRemove(fromBean.getMemberCardNumber()));
            toBean.setTotalConsumption(fromBean.getTotalConsumption());
            toBean.setDefaultTotalConsumption(fromBean.getDefaultTotalConsumption());
            toBean.setWebsite(stringRemove(fromBean.getWebsite()));
            toBean.setBillImage(stringRemove(fromBean.getBillImage()));
            if (null != fromBean.getGoodsDetails()) {
                List<GoodsDetailInfo> rawList = fromBean.getGoodsDetails();
                List<String> list = new ArrayList<>();
                for (GoodsDetailInfo raw : rawList) {
                    list.add(raw.toString());
                }
                toBean.setGoodsDetails(list);
            }
            toBean.setRoomNo(stringRemove(fromBean.getRoomNo()));
            toBean.setCheckinName(stringRemove(fromBean.getCheckinName()));
            toBean.setDeskNo(stringRemove(fromBean.getDeskNo()));
            toBean.setConsumeNum(fromBean.getConsumeNum());
            toBean.setDefaultConsumeNum(fromBean.getDefaultConsumeNum());
            toBean.setBillText(stringRemove(fromBean.getBillText()));
            toBean.setInTime(DateTimeUtils.getYmdhmsForNo(fromBean.getInTime()));
            toBean.setOutTime(DateTimeUtils.getYmdhmsForNo(fromBean.getOutTime()));
            toBean.setDefaultPrintDate(DateTimeUtils.getYmdhmsForNo(fromBean.getDefaultPrintDate()));
            toBean.setDefaultInTime(DateTimeUtils.getYmdhmsForNo(fromBean.getDefaultInTime()));
            toBean.setDefaultOutTime(DateTimeUtils.getYmdhmsForNo(fromBean.getDefaultOutTime()));
            toBean.setUploadType(stringRemove(fromBean.getUploadType()));
            toBean.setCustomRecord(fromBean.getCustomRecord());
            toBean.setBillType(stringRemove(fromBean.getBillType()));
            toBean.setModifyType(stringRemove(fromBean.getModifyType()));
            toBean.setBillSource(stringRemove(fromBean.getBillSource()));
            toBean.setAnalyzPath(stringRemove(fromBean.getAnalyzPath()));
            toBean.setBillMatchKey(stringRemove(fromBean.getBillMatchKey()));
            toBean.setMatchId(stringRemove(fromBean.getMatchId()));
            toBean.setDefaultSaleTime(DateTimeUtils.getYmdhmsForNo(fromBean.getDefaultSaleTime()));
            toBean.setCustomerName(stringRemove(fromBean.getCustomerName()));
            toBean.setMembershipId(stringRemove(fromBean.getMembershipId()));
            toBean.setMemberLevels(stringRemove(fromBean.getMemberLevels()));
            toBean.setPetName(stringRemove(fromBean.getPetName()));
            toBean.setPetNumber(stringRemove(fromBean.getPetNumber()));
            toBean.setPrincipal(stringRemove(fromBean.getPrincipal()));
            toBean.setDeposit(stringRemove(fromBean.getDeposit()));
            toBean.setPrintDate(DateTimeUtils.getYmdhmsForNo(fromBean.getPrintDate()));
            toBean.setDefaultInterceptTime(DateTimeUtils.getYmdhmsForNo(fromBean.getDefaultInterceptTime()));
            toBean.setPrintMatchType(stringRemove(fromBean.getPrintMatchType()));
            toBean.setBillMergeKey(stringRemove(fromBean.getBillMergeKey()));
            toBean.setModifyAmount(fromBean.getModifyAmount());
            toBean.setModifyAmountList(fromBean.getModifyAmountList());
            toBean.setModifyAmountSaleTimeList(DateTimeUtils.getYmdhmsForNo(fromBean.getModifyAmountSaleTimeList()));
            toBean.setModifyAmountInterceptTimeList(
                    DateTimeUtils.getYmdhmsForNo(fromBean.getModifyAmountInterceptTimeList()));
            toBean.setUniqueId(stringRemove(fromBean.getUniqueId()));
            toBean.setUniqueIdHis(fromBean.getUniqueIdHis());
            toBean.setIfqrcode(stringRemove(fromBean.getIfqrcode()));
            toBean.setCouponNum(stringRemove(fromBean.getCouponNum()));
            toBean.setErpMemberCard(stringRemove(fromBean.getErpMemberCard()));
            toBean.setVoucherType(stringRemove(fromBean.getVoucherType()));
            toBean.setQrCode(stringRemove(fromBean.getQrCode()));
            toBean.setIntegralmark(stringRemove(fromBean.getIntegralmark()));
            toBean.setThisintegral(stringRemove(fromBean.getThisintegral()));
            toBean.setRemarks(stringRemove(fromBean.getRemarks()));
            toBean.setTemplateName(stringRemove(fromBean.getTemplateName()));
            toBean.setCustName(stringRemove(fromBean.getCustName()));
            toBean.setCustName(stringRemove(fromBean.getCustTaxNo()));
            toBean.setCustAdress(stringRemove(fromBean.getCustAdress()));
            toBean.setCustBankAccount(stringRemove(fromBean.getCustBankAccount()));
            toBean.setThirdPartyOrderNo(stringRemove(fromBean.getThirdPartyOrderNo()));
            toBean.setRechargeableCardConsumeAmount(fromBean.getRechargeableCardConsumeAmount());
            toBean.setDefaultRechargeableCardConsumeAmount(fromBean.getDefaultRechargeableCardConsumeAmount());
            toBean.setOutOfPocketAmount(fromBean.getOutOfPocketAmount());
            toBean.setDefaultOutOfPocketAmount(fromBean.getDefaultOutOfPocketAmount());
            toBean.setRechargeAmount(fromBean.getRechargeAmount());
            toBean.setDefaultRechargeAmount(fromBean.getDefaultRechargeAmount());
            toBean.setMemberPrice(fromBean.getMemberPrice());
            toBean.setDefaultMemberPrice(fromBean.getDefaultMemberPrice());
            toBean.setMemberDiscountrate(fromBean.getMemberDiscountrate());
            toBean.setDefaultMemberDiscountrate(fromBean.getDefaultMemberDiscountrate());
            toBean.setMemberTotalConsumption(fromBean.getMemberTotalConsumption());
            toBean.setDefaultMemberTotalConsumption(fromBean.getDefaultMemberTotalConsumption());
            toBean.setTakeout(stringRemove(fromBean.getTakeout()));
            if (null != fromBean.getDiscountDetails()) {
                List<DiscountDetailsInfo> rawList = fromBean.getDiscountDetails();
                List<String> list = new ArrayList<>();
                for (DiscountDetailsInfo raw : rawList) {
                    list.add(raw.toString());
                }
                toBean.setDiscountDetails(list);
            }
            return toBean;
        }
    
        /**
         * 原始数据格式化处理,待完善
         * 
         * @param jsonStr
         * @return
         */
        private static String stringRemove(String strVal) {
            if (StringUtils.isEmpty(strVal)) {
                return "";
            }
            strVal = strVal.replace("	", "");
            strVal = strVal.replace("
    ", "");
            strVal = strVal.replace("
    ", "");
            return strVal;
        }
    }

    4.5、BCD裁剪实体类

    package com.mengyao.graph.etl.apps.dashboard.beans;
    
    import java.io.Serializable;
    
    /**
     * Bill Consumer Dashboard Bean
     * @author mengyao
     *
     */
    public class BCD implements Serializable {
        private static final long serialVersionUID = 1749406742944513387L;
        private String billId;
        private double receivableAmount;
        private String saleTime;
        private String sdt;//yyyyMMdd
        private int hour;
        private String billType;
        private String shopId;
        private String shopEntityId;
        public BCD() {
            super();
        }
        public BCD(String billId, double receivableAmount, String saleTime, String billType, String shopId, String shopEntityId) {
            super();
            this.billId = billId;
            this.receivableAmount = receivableAmount;
            this.saleTime = saleTime;
            setDateTime(saleTime);
            this.billType = billType;
            this.shopId = shopId;
            this.shopEntityId = shopEntityId;
        }
        public String getBillId() {
            return billId;
        }
        public void setBillId(String billId) {
            this.billId = billId;
        }
        public double getReceivableAmount() {
            return receivableAmount;
        }
        public void setReceivableAmount(double receivableAmount) {
            this.receivableAmount = receivableAmount;
        }
        public String getSaleTime() {
            return saleTime;
        }
        public void setSaleTime(String saleTime) {
            this.saleTime = saleTime;
            setDateTime(saleTime);
        }
        public String getSdt() {
            return sdt;
        }
        public void setSdt(String sdt) {
            this.sdt = sdt;
        }
        public int getHour() {
            return hour;
        }
        public void setHour(int hour) {
            this.hour = hour;
        }
        public String getBillType() {
            return billType;
        }
        public void setBillType(String billType) {
            this.billType = billType;
        }
        public String getShopId() {
            return shopId;
        }
        public void setShopId(String shopId) {
            this.shopId = shopId;
        }
        public String getShopEntityId() {
            return shopEntityId;
        }
        public void setShopEntityId(String shopEntityId) {
            this.shopEntityId = shopEntityId;
        }
        private void setDateTime(String saleTime) {
            if (null!=saleTime&&saleTime.length()==17) {
                this.sdt = saleTime.substring(0, 8);
                this.hour = Integer.parseInt(saleTime.substring(8, 10));
            }
        }
        @Override
        public String toString() {
            return  billId + "	" + receivableAmount + "	" + saleTime + "	" + sdt + "	" + billType + "	" + shopId + "	" + shopEntityId;
        }
        
    }

    4.6、销售分析业务实现类

    package com.mengyao.graph.etl.apps.dashboard.service;
    
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;
    
    import org.apache.spark.sql.Column;
    import org.apache.spark.sql.Dataset;
    import org.apache.spark.sql.Row;
    import org.apache.spark.sql.functions;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.mengyao.graph.etl.apps.dashboard.beans.AreaProjSale;
    import com.mengyao.graph.etl.apps.dashboard.beans.AreaSaleTrendAll;
    import com.mengyao.graph.etl.apps.dashboard.beans.PeakTime;
    import com.mengyao.graph.etl.apps.dashboard.beans.ProjectTypeShopNumber;
    import com.mengyao.graph.etl.apps.dashboard.beans.ShopSaleRank;
    import com.mengyao.graph.etl.apps.dashboard.beans.TotalRefund;
    import com.mengyao.graph.etl.apps.dashboard.beans.TotalSale;
    import com.mengyao.graph.etl.apps.dashboard.beans.TotalSettlementBillNumber;
    import com.mengyao.utils.RedisUtil;
    
    
    /**
     * 大屏指标分析
     * @author mengyao
     *
     */
    public class SaleAnalysisService implements Serializable {
    
        private static final long serialVersionUID = 8289368096001689148L;
        //解决区域名称hash重复问题
        private static final String SALT="aAb12";
    
        /**
         * 每日重置大屏指标值
         */
        @Deprecated
        public void reset() {
            RedisUtil.setObject("dtsbn_6", "{"结账单数":{"val":7270}}"");
            RedisUtil.setObject("dpt_7", "{"高峰时段":{"val":13}}"");
            RedisUtil.setObject("dtr_5", "{"退款金额":{"val":7301.8}}"");
            RedisUtil.setObject("dts_4", "{"总销售额":{"val":1300523.8000000005}}"");
            RedisUtil.setObject("curDay", "20190227"");
            RedisUtil.setObject("dastfa_2_20190227", "{"各区域销售额发展趋势":{"重庆天地":[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,9.5,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0],"创智天地":[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,43.3,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0],"上海瑞虹":[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,16.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0],"上海新天地":[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,553.75,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]}}"");
            RedisUtil.setObject("dasp_1", "{"各区域销售额占比":{"重庆天地":[{"bn":1,"n":"重庆天地","sa":9.5}],"创智天地":[{"bn":3,"n":"壹方","sa":43.3}],"上海新天地":[{"bn":4,"n":"新天地时尚购物中心","sa":152.0},{"bn":3,"n":"上海新天地南里北里","sa":74.0},{"bn":2,"n":"湖滨道购物中心","sa":100.0},{"bn":10,"n":"新天地广场","sa":227.75}],"上海瑞虹":[{"bn":1,"n":"瑞虹天地星星堂","sa":16.0}]}}"");
            RedisUtil.setObject("dpc_8", "{"各项目销售额对比":{"重庆天地":[{"bn":1,"n":"重庆天地","sa":9.5}],"创智天地":[{"bn":3,"n":"壹方","sa":43.3}],"上海新天地":[{"bn":4,"n":"新天地时尚购物中心","sa":152.0},{"bn":3,"n":"上海新天地南里北里","sa":74.0},{"bn":2,"n":"湖滨道购物中心","sa":100.0},{"bn":10,"n":"新天地广场","sa":227.75}],"上海瑞虹":[{"bn":1,"n":"瑞虹天地星星堂","sa":16.0}]}}"");
            RedisUtil.setObject("dpac_10", "{"各项目单均消费":{"重庆天地":[{"bn":1,"n":"重庆天地","sa":9.5}],"创智天地":[{"bn":3,"n":"壹方","sa":43.3}],"上海新天地":[{"bn":4,"n":"新天地时尚购物中心","sa":152.0},{"bn":3,"n":"上海新天地南里北里","sa":74.0},{"bn":2,"n":"湖滨道购物中心","sa":100.0},{"bn":10,"n":"新天地广场","sa":227.75}],"上海瑞虹":[{"bn":1,"n":"瑞虹天地星星堂","sa":16.0}]}}"");
            RedisUtil.setObject("dsfst_9", "{"店铺销售排行":[{"pn":"新天地时尚购物中心","sa":152.0,"sn":"GREYBOX COFFEE"},{"pn":"新天地广场","sa":114.75,"sn":"Arabica"},{"pn":"湖滨道购物中心","sa":100.0,"sn":"LOKAL"},{"pn":"上海新天地南里北里","sa":74.0,"sn":"哈肯铺_手感烘焙"},{"pn":"壹方","sa":60.3,"sn":"新一天便利"},{"pn":"新天地广场","sa":41.0,"sn":"奈雪的茶"},{"pn":"新天地广场","sa":39.0,"sn":"蒲石小点"},{"pn":"新天地广场","sa":18.0,"sn":"Fresh_Every_Day"},{"pn":"瑞虹天地星星堂","sa":16.0,"sn":"老盛昌"},{"pn":"新天地广场","sa":15.0,"sn":"多几谷"}]}"");
            RedisUtil.setObject("dptsc_11", "{"各项目业态销售额对比":{"上海新天地南里北里":[{"n":"餐饮","sa":74.0,"sn":3}],"新天地广场":[{"n":"餐饮","sa":227.75,"sn":10}],"重庆天地":[{"n":"其它","sa":9.5,"sn":0}],"湖滨道购物中心":[{"n":"餐饮","sa":100.0,"sn":2}],"壹方":[{"n":"餐饮","sa":43.3,"sn":3}],"瑞虹天地星星堂":[{"n":"餐饮","sa":16.0,"sn":1}],"新天地时尚购物中心":[{"n":"餐饮","sa":152.0,"sn":4}]}}"");
            RedisUtil.setObject("dpsn_12", "{"各项目店铺数量":{"上海新天地南里北里":[{"n":"餐饮","sa":74.0,"sn":3}],"新天地广场":[{"n":"餐饮","sa":227.75,"sn":10}],"重庆天地":[{"n":"其它","sa":9.5,"sn":0}],"湖滨道购物中心":[{"n":"餐饮","sa":100.0,"sn":2}],"壹方":[{"n":"餐饮","sa":43.3,"sn":3}],"瑞虹天地星星堂":[{"n":"餐饮","sa":16.0,"sn":1}],"新天地时尚购物中心":[{"n":"餐饮","sa":152.0,"sn":4}]}}"");
        }
        
        /**
         * 总销售额
         * @param ds
         */
        public void totalSale(Dataset<Row> bill) {
            Row[] rows = (Row[])bill
                    .filter("billType=1 and receivableAmount>=0")
                    .agg(functions.sum(new Column("receivableAmount")).alias("totalSale"), functions.count("receivableAmount"))
                    .head(1);
            Map<String, TotalSale> map = new HashMap<String, TotalSale>();
            double totalSale=0D;
            if(rows.length>0){
                Row row = rows[0];
                if (!row.isNullAt(0)) {
                    //销售额
                    totalSale=row.getDouble(0);
                }
                if (!row.isNullAt(1)) {
                    //结账单数
                    totalSettlementBillNumber(new TotalSettlementBillNumber(row.getLong(1)));
                }
            }
            map.put("总销售额", new TotalSale(totalSale));
            String str = JSONObject.toJSONString(map);
            System.out.println("====####--totalSale##" + str);
            // 将总销售额存入redis
            RedisUtil.setObject("dts_4", str);
        }
        
        /**
         * 退款金额
         * @param ds
         */
        public void totalRefund(Dataset<Row> bill) {
            Row[] rows = (Row[]) bill
                    .filter("((billType=6) or (billType=1 and receivableAmount<0))")
                    .agg(functions.sum(functions.abs(new Column("receivableAmount"))).alias("totalSale"))
                    .head(1);
            Map<String, TotalRefund> map=new HashMap<>();
            map.put("退款金额", new TotalRefund(0));
            if(rows.length>0){
                Row row = rows[0];
                if (!row.isNullAt(0)) {
                    map.put("退款金额", new TotalRefund(row.getDouble(0)));
                }
            }
            String str=JSONObject.toJSONString(map);
            System.out.println("====####--totalRefund##"+str);
            //将退款金额存入redis
            RedisUtil.setObject("dtr_5",str);
        }
        
        /**
         * 高峰时段
         *  1、时段:小时;
         *    2、高峰:当日每小时结账单数累计最大;
         *    3、高峰时段:所有mall累计每小时结账单数;
         * @param ds
         * @return {"高峰时段":{"val":12}}
         */
        public void peakTime(Dataset<Row> bill) {
            //账单表本身有mallid(shopId) 因此无需关联店铺表
            Map<String, PeakTime> map=new HashMap<>();
            map.put("高峰时段", new PeakTime(8));
            Row[] rows = (Row[])bill
                    .filter("billType=1")
                    .groupBy("hour")
                    .agg(functions.sum("receivableAmount").alias("totalSale"))
                    .orderBy(new Column("totalSale").desc())
                    .limit(1)
                    .head(1);
            if(rows.length>0){
                Row row = rows[0];
                if (!row.isNullAt(0)) {
                    map.put("高峰时段", new PeakTime(row.getAs(0)));
                }
            }
            String str=JSONObject.toJSONString(map);
            System.out.println("====####--peakTime##"+str);
            //将高峰时段放入redis
            RedisUtil.setObject("dpt_7", str);
        }
        
        /**
         * 各区域销售发展趋势-多个区域(每个区域下有多个mall)当日0点~24点的累计销售额
         * @param bill
         * @param ruian
         * @param curDay
         */
        public void areaSaleTrendForAll(Dataset<Row> bill, Dataset<Row> ruian,Set<String> areaSet, String curDay) {
            Row[] rows = (Row[])bill
                    .filter("billType=1 and receivableAmount>0")
                    .join(ruian, bill.col("shopId").equalTo(ruian.col("rmid")), "leftouter")
                    .groupBy("area_cn", "hour")
                    .agg(functions.sum("receivableAmount").alias("areaDayHourSale"))
                    .orderBy("areaDayHourSale")
                    .select("area_cn","areaDayHourSale","hour")
                    .collect();
    //        Map<String, Map<String,Map<Integer, Double>>> map = new HashMap<>();
            Map<String, Map<String,Collection<Double>>> map = new HashMap<>();
            Map<String,AreaSaleTrendAll> maps=new HashMap<>();
            if(rows.length>0){
                for (Row row : rows) {
                    String areaCn=null;
                    double areaDayHourSale=0D;
                    int saleHour=0;
                    if(!row.isNullAt(0)){
                        areaCn=row.getString(0);
                    }
                    if(!row.isNullAt(1)){
                        areaDayHourSale=row.getDouble(1);
                    }
                    if(!row.isNullAt(2)){
                        saleHour=row.getInt(2);
                    }
                    if(maps.containsKey(areaCn+SALT)){
                        AreaSaleTrendAll ast=maps.get(areaCn+SALT);
                        ast.getVals().put(saleHour, areaDayHourSale);
                    }else{
                        HashMap<Integer,Double> vals=new HashMap<Integer,Double>();
                        vals.put(saleHour, areaDayHourSale);
                        maps.put(areaCn+SALT, new AreaSaleTrendAll(areaCn, saleHour, areaDayHourSale));
                    }
                }
            }
            //填充没有销售额的区域记录,使数据更加完整。
            areaSet.forEach(areaCn->{
                if(!maps.containsKey(areaCn+SALT)){
                    maps.put(areaCn+SALT, new AreaSaleTrendAll(areaCn));
                }
            });
    
            System.out.println("==####========填充hou====begin=====================");
            for (String key:maps.keySet()) {
                System.out.println("Key:"+key);
            }
            System.out.println("==####========填充hou=====end====================");
            
    //        Map<String,Map<Integer, Double>> rs=new HashMap<>();
    //        for(AreaSaleTrendAll asta:maps.values()){
    //            rs.put(asta.getAreaCn(), asta.getVals());
    //        }
            Map<String,Collection<Double>> rs=new HashMap<>();
            for(AreaSaleTrendAll asta:maps.values()){
                rs.put(asta.getAreaCn(), asta.getVals().values());
            }
            map.put("各区域销售额发展趋势", rs);
            //转成json 字符串
            String str=JSONObject.toJSONString(map);
            System.out.println("====####--areaSaleTrendForAll##"+str);
            //放入redis
            RedisUtil.setObject("dastfa_2_"+curDay, str);
            //维护redis中最新日期
            if(!RedisUtil.existsObject("curDay")){//如果为空,说明是第一次运行,直接将当前日期设置到redis中
                RedisUtil.setObject("curDay", curDay);
            }else{
                String curDayRedis=(String) RedisUtil.getObject("curDay");
                if((curDayRedis.compareTo(curDay))<0){//说明当前日期大于redis中的日期,更新
                    RedisUtil.setObject("curDay", curDay);
                }
            }
        }
    
        /**
         * 各项目销售额对比
         *     1、各项目:各个mall;
         *    2、单一项目销售额:mall的当日开始营业时间到当前时间累计销售额;
         */
        public void projectContrast(Dataset<Row> bill, Dataset<Row> ruian,Map<String, Set<String>> ruianMallAll) {
            Row[] rows = (Row[])bill
                    .filter("billType=1 and receivableAmount>0")
                    .join(ruian, bill.col("shopId").equalTo(ruian.col("rmid")), "leftouter")
                    .groupBy("name", "area_cn")
                    .agg(functions.sum("receivableAmount").alias("mallDaySale"), functions.count("receivableAmount").alias("mallDayBillNum"))
    //                .orderBy("area_cn")
                    .select("name","area_cn","mallDaySale","mallDayBillNum")
                    .collect();
            Map<String,List<AreaProjSale>> rs=new HashMap<>();
            if(rows.length>0) {
                for (Row row : rows) {
                    String mallName=null;
                    String areaCn=null;
                    double mallDaySale=0D;
                    long mallDayBillNum=0L;
                    AreaProjSale obj=new AreaProjSale();
                    if(!row.isNullAt(0)){//项目、mall的名称
                        mallName=row.getString(0);
                        obj.setN(mallName);
                    }
                    if(!row.isNullAt(1)){//区域名称
                        areaCn=row.getString(1);
                    }
                    if(!row.isNullAt(2)){//mall的日销售额
                        mallDaySale=row.getDouble(2);
                        obj.setSa(mallDaySale);
                    }
                    if(!row.isNullAt(3)){//mall的日结账单数
                        mallDayBillNum=row.getLong(3);
                        obj.setBn(mallDayBillNum);
                    }
                    //判断是否有该区域
                    if(rs.containsKey(areaCn)){
                        rs.get(areaCn).add(obj);
                    }else{//不存在该区域
                        List<AreaProjSale> list=new ArrayList<>();
                        list.add(obj);
                        rs.put(areaCn, list);
                    }
                }
            }
            //填充未产生账单的数据,默认0
            //获取全部瑞安的区域和mallName的集合
            Set<Entry<String,Set<String>>> entrySet = ruianMallAll.entrySet();
            for(Map.Entry<String, Set<String>> entry:entrySet){
                String areaCn=entry.getKey();//获取区域名称
                //获取每个区域的标准mall的集合
                Set<String> mallSet=entry.getValue();
                if(rs.containsKey(areaCn)){//实际数据中已经存在该区域相关数据
                    //判断实际数据mall是否完整
                    //用来存放实际数据中mallname的集合
                    Set<String> mallSetCur=new HashSet<>();
                    //用来存放没有账单的mall的集合
                    Set<String> mallSetNew=new HashSet<>();
                    //遍历该区域下实际数据集合 并填充set
                    for(AreaProjSale obj:rs.get(areaCn)){
                        mallSetCur.add(obj.getN());
                    }
                    //求两个set的差集合  将标准数据放入
                    mallSetNew.addAll(mallSet);
                    //求差集合  标准数据-实际数据 得到差集
                    mallSetNew.removeAll(mallSetCur);
                    //遍历差集合 ,填充默认值
                    mallSetNew.forEach(mn->{
                        rs.get(areaCn).add(new AreaProjSale(mn));
                    });
                }else{//该区域不存在
                    //便利该区域下标准mall集合,逐个放入
                    List<AreaProjSale> list=new ArrayList<>();
                    mallSet.forEach(mn->{
                        list.add(new AreaProjSale(mn));
                    });
                    rs.put(areaCn, list);
                }
            }
            //各区域销售额占比
            areaSaleProportion(rs);
            //各区域单均消费
            projectAvgConsumer(rs);
            //转成json
            Map<String, Map<String,List<AreaProjSale>>> pmap=new HashMap<>();
            pmap.put("各项目销售额对比", rs);
            //转成json
            String str=JSONObject.toJSONString(pmap);
            System.out.println("====####--projectContrast##"+str);
            //1 8各项目销售额对比dpc_8 
            RedisUtil.setObject("dpc_8", str);
        }
        
        /**
         * 所有mall中销售额最高的top10店铺
         * @param bill
         * @param shop
         * @param ruian
         */
        public void saleForShopTop10(Dataset<Row> bill, Dataset<Row> shop, Dataset<Row> ruian) {
            Row[] rows = (Row[])bill
                    .filter("billType=1 and receivableAmount>0")
                    .join(shop, bill.col("shopEntityId").equalTo(shop.col("shop_entity_id")), "leftouter")
                    .join(ruian, bill.col("shopId").equalTo(ruian.col("rmid")), "leftouter")
                    .groupBy("shop_entity_name", "name")
                    .agg(functions.sum("receivableAmount").alias("shopDaySale"))
                    .orderBy(new Column("shopDaySale").desc())
                    .select("shop_entity_name","name","shopDaySale")
                    .limit(10)
                    .head(10);
            List<ShopSaleRank> ssrList=new LinkedList<>();
            if(rows.length>0) {
                for (Row row : rows) {
                    ShopSaleRank ssr=new ShopSaleRank();
                    if(!row.isNullAt(0)){//店铺名称
                        ssr.setSn(row.getString(0));
                    }
                    if(!row.isNullAt(1)){//项目/mall名称
                        ssr.setPn(row.getString(1));
                    }
                    if(!row.isNullAt(2)){//店铺日销售额
                        ssr.setSa(row.getDouble(2));
                    }
                    ssrList.add(ssr);
                }
            }
            //转成json
            Map<String, List<ShopSaleRank>> pmap=new HashMap<>();
            pmap.put("店铺销售排行", ssrList);
            String str=JSON.toJSONString(pmap);
            System.out.println("====####--saleForShopTop10##"+str);
            //将10个店铺日销售额放入redis
            RedisUtil.setObject("dsfst_9",str);
        }
        
        /**
         * 各项目业态销售额对比
         * 
         * @param session
         * @param beginYMDH
         * @param endYMDH
         */
        public void projectTypeSaleContrast(Dataset<Row> bill, Dataset<Row> shop, Dataset<Row> type, Dataset<Row> ruian,
                Set<String> areaSet,Set<String> mallSet,Set<String> ruianTypeAll) {
            Row[] rows = (Row[])bill
                    .filter("billType=1 and receivableAmount>0")
                    .join(shop, shop.col("shop_entity_id").equalTo(bill.col("shopEntityId")), "leftouter")
                    .join(type, type.col("id").equalTo(shop.col("shop_entity_type_root")), "leftouter")
                    .join(ruian, ruian.col("rmid").equalTo(bill.col("shopId")))
                    .groupBy("name", "shop_type_name")
                    .agg(functions.sum("receivableAmount").alias("shopTypeDaySale"), functions.countDistinct("shop_entity_id").alias("shopNum"))
                    .select("name","shop_type_name","shopTypeDaySale","shopNum")
                    .collect();
            Map<String,List<ProjectTypeShopNumber>> map=new HashMap<>();
            if(rows.length>0) {
                for (Row row : rows) {
                    ProjectTypeShopNumber pac=new ProjectTypeShopNumber();
                    String mallName=null;
                    if(!row.isNullAt(0)){//项目、mall名称
                        mallName=row.getString(0);
                    }
                    if(!row.isNullAt(1)){//业态名称
                        pac.setN(row.getString(1));
                    }else{
                        pac.setN("其它");
                    }
                    if(!row.isNullAt(2)){//mall的业态日销售额
                        pac.setSa(row.getDouble(2));
                    }
                    if(!row.isNullAt(3)){//mall的业态店铺数量
                        pac.setSn(row.getLong(3));
                    }
                    if(map.containsKey(mallName)){//更新map中的list列表
                        List<ProjectTypeShopNumber> pacList=map.get(mallName);
                        pacList.add(pac);
                    }else{//新的mall 新建列表放入map
                        List<ProjectTypeShopNumber> pacList=new LinkedList<>();
                        pacList.add(pac);
                        map.put(mallName, pacList);
                    }
                }
            }
            //为没有产生账单的业态或mall填充默认数据,是数据看起来完整
            mallSet.forEach(mallName->{
                if(!map.containsKey(mallName)) {//没有账单的mall
                    List<ProjectTypeShopNumber> list=new ArrayList<>();
                    //遍历全量业态,进行填充
                    ruianTypeAll.forEach(type_->{
                        list.add(new ProjectTypeShopNumber(type_,0.0,0));
                    });
                    map.put(mallName,list);
                }else{//有账单的mall
                    //用来获取实际账单数据中已存在的业态
                    Set<String> ruianTypeCur = new HashSet<>();
                    //用来存放没有账单的业态
                    Set<String> ruianTypeNew = new HashSet<>();
                    //遍历数据,提取实际数据中的业态
                    for(ProjectTypeShopNumber obj:map.get(mallName)){
                        ruianTypeCur.add(obj.getN());
                    }
                    //将产生账单的业态和全量业态求差即
                    ruianTypeNew.addAll(ruianTypeAll);
                    ruianTypeNew.removeAll(ruianTypeCur);
                    //将没有实际账单的业态数据填从(默认值填充)                            
                    for(String type_:ruianTypeNew){
                        map.get(mallName).add(new ProjectTypeShopNumber(type_,0.0,0));
                    }
                }
            });
            //为==各项目店铺数量==填充数据
            projectShopNumber(map);
            //转成json
            Map<String, Map<String,List<ProjectTypeShopNumber>>> pmap=new HashMap<>();
            pmap.put("各项目业态销售额对比", map);
            String str=JSON.toJSONString(pmap);
            System.out.println("====####--projectTypeSaleContrast##"+str);
            RedisUtil.setObject("dptsc_11",str);
        }
        
        //=============================================================================================================
        /**
         * 结账单数
         * 
         * @param tsb
         */
        public void totalSettlementBillNumber(TotalSettlementBillNumber tsb) {
             Map<String, TotalSettlementBillNumber> map=new HashMap<>();
             map.put("结账单数", tsb);
             String str=JSONObject.toJSONString(map);
             System.out.println("====####--totalSettlementBillNumber##"+str);
             //将结账单数放入redis
             RedisUtil.setObject("dtsbn_6", str);
        }
        
        /**
         * 各区域销售占比
         * 直接将数据封装成json,无需额外处理
         * 
         * @param rs 已经封装好的数据,请参考{@link com.mengyao.graph.etl.apps.dashboard.service.SaleAnalysisService.projectContrast()}
         */
        private void areaSaleProportion(Map<String, List<AreaProjSale>> rs) {
            Map<String, Map<String,List<AreaProjSale>>> pmap=new HashMap<>();
            pmap.put("各区域销售额占比 ", rs);
            //转成json
            String str=JSONObject.toJSONString(pmap);
            System.out.println("====####--areaSaleProportion##"+str);
            //各区域销售占比dasp_1
            RedisUtil.setObject("dasp_1", str);
        }
        
        /**
         *  各项目单均消费
         *  版本二 营业时间24小时制
         *    1、各项目:各个mall;
         *    2、单均消费:mall的当日开始营业时间到当前时间累计销售额,应收额累计/结账单累计;
         *  直接将数据封装成json,无需额外处理
         *  
         *  @param rs 已经封装好的数据,请参考{@link com.mengyao.graph.etl.apps.dashboard.service.SaleAnalysisService.projectContrast()}
         */
        private void projectAvgConsumer(Map<String, List<AreaProjSale>> rs) {
            Map<String, Map<String,List<AreaProjSale>>> pmap=new HashMap<>();
            pmap.put("各项目单均消费", rs);
            //转成json
            String str=JSONObject.toJSONString(pmap);
            System.out.println("====####--projectAvgConsumer##"+str);
            //10各项目单均消费dpac_10
            RedisUtil.setObject("dpac_10", str);        
        }
        
        /**
         * 各项目店铺数量
         * 数据填充自上面的方法
         * 
         * {@link projectTypeSaleContrast(SparkSession session,String beginYMDH,String endYMDH)}
         */
        private void projectShopNumber(Map<String,List<ProjectTypeShopNumber>> map) {
            //转成json
            Map<String, Map<String,List<ProjectTypeShopNumber>>> pmap=new HashMap<>();
            pmap.put("各项目店铺数量", map);
            String str=JSON.toJSONString(pmap);
            System.out.println("====####--projectShopNumber##"+str);
            RedisUtil.setObject("dpsn_12",str);
        }
        
        
    }
  • 相关阅读:
    mysql 表映射为java bean 手动生成。
    MySQL 存储修改
    jdk 8 日期处理。
    jsp jstl quote symbol expected
    spring boot 接口用例测试
    spring boot js 文件引用 单引问题。
    spring boot 自定义视图路径
    spring 事务回滚。
    Eclipse svn 项目 星号
    Codeforces Round #277.5 (Div. 2)-B. BerSU Ball
  • 原文地址:https://www.cnblogs.com/mengyao/p/10155494.html
Copyright © 2020-2023  润新知