wcc 3 éve
szülő
commit
13b873651a
16 módosított fájl, 2893 hozzáadás és 626 törlés
  1. 17 135
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/AdStatJob.java
  2. 111 339
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/PlanStatJob.java
  3. 52 0
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/kafka/KafkaComponent.java
  4. 18 0
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/kafka/KafkaProperties.java
  5. 4 11
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/pojo/entity/PlanStatOfDayDWD.java
  6. 46 87
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/pojo/entity/PlanStatOfHourDWD.java
  7. 2383 0
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/pojo/entity/PlanStatOfMinuteDWD.java
  8. 0 10
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/pojo/properties/KafkaProperties.java
  9. 23 12
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/process/AdMinuteDWDProcess.java
  10. 139 0
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/process/PlanMinuteDWDProcess.java
  11. 38 0
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/trigger/AdMinuteODSStreamTrigger.java
  12. 38 0
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/trigger/PlanMinuteODSStreamTrigger.java
  13. 18 5
      flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/util/PlanUtil.java
  14. 0 13
      flink-ad-monitoring/src/main/resources/ad_stream_of_day.properties
  15. 0 13
      flink-ad-monitoring/src/main/resources/ad_stream_of_minute.properties
  16. 6 1
      flink-ad-monitoring/src/main/resources/application.properties

+ 17 - 135
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/AdStatJob.java

@@ -13,16 +13,16 @@ import flink.zanxiangnet.ad.monitoring.pojo.dto.AdDataOfDayDTO;
 import flink.zanxiangnet.ad.monitoring.pojo.dto.AdStatOfDayODSDTO;
 import flink.zanxiangnet.ad.monitoring.pojo.entity.*;
 import flink.zanxiangnet.ad.monitoring.pojo.properties.ApplicationProperties;
-import flink.zanxiangnet.ad.monitoring.pojo.properties.KafkaProperties;
 import flink.zanxiangnet.ad.monitoring.pojo.dto.AdDataOfMinuteDTO;
 import flink.zanxiangnet.ad.monitoring.process.AdMinuteDWDProcess;
 import flink.zanxiangnet.ad.monitoring.sink.TunnelBatchStreamSink;
 import flink.zanxiangnet.ad.monitoring.stream.KeyedBatchStream;
+import flink.zanxiangnet.ad.monitoring.trigger.AdMinuteODSStreamTrigger;
 import flink.zanxiangnet.ad.monitoring.util.DateUtil;
 import flink.zanxiangnet.ad.monitoring.util.JsonUtil;
+import flink.zanxiangnet.ad.monitoring.kafka.KafkaComponent;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.flink.api.common.eventtime.*;
-import org.apache.flink.api.common.serialization.SimpleStringSchema;
 import org.apache.flink.api.common.state.MapState;
 import org.apache.flink.api.common.state.MapStateDescriptor;
 import org.apache.flink.api.common.state.ValueState;
@@ -30,7 +30,6 @@ import org.apache.flink.api.common.state.ValueStateDescriptor;
 import org.apache.flink.api.common.typeinfo.Types;
 import org.apache.flink.configuration.Configuration;
 import org.apache.flink.connector.kafka.source.KafkaSource;
-import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer;
 import org.apache.flink.streaming.api.datastream.DataStream;
 import org.apache.flink.streaming.api.datastream.DataStreamSource;
 import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
@@ -39,18 +38,9 @@ import org.apache.flink.streaming.api.functions.ProcessFunction;
 import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
 import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
 import org.apache.flink.streaming.api.windowing.time.Time;
-import org.apache.flink.streaming.api.windowing.triggers.Trigger;
-import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
 import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
-import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
 import org.apache.flink.util.Collector;
 import org.apache.flink.util.OutputTag;
-import org.apache.kafka.clients.CommonClientConfigs;
-import org.apache.kafka.clients.consumer.ConsumerConfig;
-import org.apache.kafka.clients.consumer.OffsetResetStrategy;
-import org.apache.kafka.clients.producer.ProducerConfig;
-import org.apache.kafka.common.config.SaslConfigs;
-import org.apache.kafka.common.config.SslConfigs;
 import org.springframework.beans.BeanUtils;
 
 import java.time.Duration;
@@ -68,9 +58,6 @@ public class AdStatJob {
     private static final String OLDEST_DAY = "2019-01-01";
 
     public static void main(String[] args) throws Exception {
-        System.setProperty("javax.net.ssl.trustStore", "D:\\Downloads\\kafka.client.truststore.jks");
-        // System.setProperty("javax.net.ssl.trustStore", "/root/flink-1.13.2/kafka.client.truststore.jks");
-        System.setProperty("javax.net.ssl.trustStorePassword", "KafkaOnsClient");
         StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
 
         // 加载配置文件到 flink的全局配置中
@@ -83,10 +70,7 @@ public class AdStatJob {
         });
         env.getConfig().setGlobalJobParameters(configuration);
 
-        Properties adStreamOfMinuteProps = new Properties();
-        adStreamOfMinuteProps.load(AdStatJob.class.getResourceAsStream("/ad_stream_of_minute.properties"));
-        KafkaSource<String> adStreamOfMinuteSource = buildKafkaSource(adStreamOfMinuteProps);
-
+        KafkaSource<String> adStreamOfMinuteSource = KafkaComponent.buildKafkaSource(props, KafkaComponent.KafkaTopic.adHourTopic, KafkaComponent.KafkaTopic.KafkaGroupId.adHourConsumerGroup);
         DataStreamSource<String> adStreamOfMinuteIn = env.fromSource(adStreamOfMinuteSource, WatermarkStrategy.noWatermarks(), "adStreamOfMinuteSource_kafka");
 
         // 广告分钟数据(前 5分钟的广告消耗数据)
@@ -142,41 +126,12 @@ public class AdStatJob {
 
         SingleOutputStreamOperator<AdStatOfMinuteDWD> adMinuteDWDStream = adMinuteODSStream
                 // 打水印,允许数据延迟 6分钟,同时指定时间流
-                .assignTimestampsAndWatermarks(WatermarkStrategy.<AdDataOfMinuteODS>forBoundedOutOfOrderness(Duration.ofMinutes(1L))
+                .assignTimestampsAndWatermarks(WatermarkStrategy.<AdDataOfMinuteODS>forBoundedOutOfOrderness(Duration.ofMinutes(6L))
                         .withTimestampAssigner((SerializableTimestampAssigner<AdDataOfMinuteODS>) (adODS, l) -> adODS.getStatTime()))
                 .keyBy(AdDataOfMinuteODS::getAdId)
                 // 开一个 5分钟的滚动窗口
                 .window(TumblingEventTimeWindows.of(Time.minutes(5L)))
-                .trigger(new Trigger<AdDataOfMinuteODS, TimeWindow>() {
-                    @Override
-                    public TriggerResult onElement(AdDataOfMinuteODS adDataOfMinuteODS, long time, TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
-                        // FIXME 水印时间没有意义!拿到数据是 Long.MAX
-                        if (timeWindow.maxTimestamp() <= triggerContext.getCurrentWatermark()) {
-                            // 到了窗口的最大生命周期
-                            return TriggerResult.FIRE_AND_PURGE;
-                        }
-                        triggerContext.registerEventTimeTimer(timeWindow.maxTimestamp());
-                        if (adDataOfMinuteODS.getHour() == DateUtil.milliToLocalDateTime(time).getHour()) {
-                            return TriggerResult.FIRE;
-                        }
-                        return TriggerResult.CONTINUE;
-                    }
-
-                    @Override
-                    public TriggerResult onProcessingTime(long time, TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
-                        return TriggerResult.CONTINUE;
-                    }
-
-                    @Override
-                    public TriggerResult onEventTime(long time, TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
-                        return time == timeWindow.maxTimestamp() ? TriggerResult.FIRE : TriggerResult.CONTINUE;
-                    }
-
-                    @Override
-                    public void clear(TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
-                        triggerContext.deleteEventTimeTimer(timeWindow.maxTimestamp());
-                    }
-                })
+                .trigger(new AdMinuteODSStreamTrigger())
                 .process(new AdMinuteDWDProcess());
         // .addSink(new TunnelBatchSink<>(AdStatOfMinuteDWD.class, 30000L, 365L, 6));
         new KeyedBatchStream<>("adMinuteDWDStream", adMinuteDWDStream.keyBy(AdStatOfMinuteDWD::getStatDay), 4000L, 60 * 1000L)
@@ -259,9 +214,7 @@ public class AdStatJob {
 
 
         // ------------------------------------------------------- 处理广告的天数据 -----------------------------------------
-        Properties adStreamOfDayProps = new Properties();
-        adStreamOfDayProps.load(AdStatJob.class.getResourceAsStream("/ad_stream_of_day.properties"));
-        KafkaSource<String> adStreamOfDaySource = buildKafkaSource(adStreamOfDayProps);
+        KafkaSource<String> adStreamOfDaySource = KafkaComponent.buildKafkaSource(props, KafkaComponent.KafkaTopic.adDayTopic, KafkaComponent.KafkaTopic.KafkaGroupId.adDayConsumerGroup);
 
         DataStreamSource<String> adStreamOfDayIn = env.fromSource(adStreamOfDaySource, WatermarkStrategy.noWatermarks(), "adStreamOfMinuteSource_kafka");
 
@@ -318,7 +271,7 @@ public class AdStatJob {
                 .keyBy(AdDataOfDayODS::getAdId)
                 .countWindow(1).process(new ProcessWindowFunction<AdDataOfDayODS, AdStatOfDayDWD, Long, GlobalWindow>() {
                     private Odps odps;
-                    // 上次查询的天数据
+                    // 上次查询的时间
                     private ValueState<String> lastQueryDayState;
                     // 之前聚合的昨天的数据
                     private MapState<String, AdStatOfDayDWD> historyReduceState;
@@ -382,13 +335,19 @@ public class AdStatJob {
                 .keyBy(AdDataOfDayODS::getAdId)
                 .countWindow(1).process(new ProcessWindowFunction<AdDataOfDayODS, AdStatOfDayDWD, Long, GlobalWindow>() {
                     // 上次聚合的结果
-                    private AdStatOfDayDWD lastReduce;
+                    private ValueState<AdStatOfDayDWD> lastReduceState;
+
+                    public void open(Configuration conf) {
+                        lastReduceState = getRuntimeContext().getState(new ValueStateDescriptor<>("lastReduceState", AdStatOfDayDWD.class));
+                    }
 
                     @Override
-                    public void process(Long elementsCount, ProcessWindowFunction<AdDataOfDayODS, AdStatOfDayDWD, Long, GlobalWindow>.Context context, Iterable<AdDataOfDayODS> elements, Collector<AdStatOfDayDWD> out) {
+                    public void process(Long elementsCount, ProcessWindowFunction<AdDataOfDayODS, AdStatOfDayDWD, Long, GlobalWindow>.Context context,
+                                        Iterable<AdDataOfDayODS> elements, Collector<AdStatOfDayDWD> out) throws Exception {
                         AdDataOfDayODS element = elements.iterator().next();
-                        lastReduce = AdStatOfDayDWD.reduce(lastReduce, element, System.currentTimeMillis());
-                        out.collect(lastReduce);
+                        AdStatOfDayDWD newStatDWD = AdStatOfDayDWD.reduce(lastReduceState.value(), element, System.currentTimeMillis());
+                        lastReduceState.update(newStatDWD);
+                        out.collect(newStatDWD);
                     }
                 });
         //.addSink(new TunnelBatchSink<>(AdStatOfDayDWD.class, 30000L, 365L, 6));
@@ -398,81 +357,4 @@ public class AdStatJob {
 
         env.execute();
     }
-
-    private static KafkaSource<String> buildKafkaSource(Properties props) {
-        return buildKafkaSource(props.getProperty(KafkaProperties.KAFKA_SERVERS),
-                props.getProperty(KafkaProperties.KAFKA_USERNAME),
-                props.getProperty(KafkaProperties.KAFKA_PASSWORD),
-                props.getProperty(KafkaProperties.KAFKA_SSL_PATH),
-                props.getProperty(KafkaProperties.KAFKA_TOPIC),
-                props.getProperty(KafkaProperties.KAFKA_GROUP_ID)
-        );
-    }
-
-    /**
-     * 内网
-     *
-     * @param servers
-     * @param username
-     * @param password
-     * @param sslPath
-     * @param topic
-     * @param groupId
-     * @return
-     */
-    private static KafkaSource<String> buildKafkaSourceVPC(String servers, String username, String password, String sslPath, String topic, String groupId) {
-        Properties props = new Properties();
-        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
-        //两次Poll之间的最大允许间隔。
-        //消费者超过该值没有返回心跳,服务端判断消费者处于非存活状态,服务端将消费者从Group移除并触发Rebalance,默认30s。
-        props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 30000);
-        //每次Poll的最大数量。
-        //注意该值不要改得太大,如果Poll太多数据,而不能在下次Poll之前消费完,则会触发一次负载均衡,产生卡顿。
-        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 30);
-        //消息的反序列化方式。
-        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
-        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
-        //当前消费实例所属的消费组,请在控制台申请之后填写。
-        //属于同一个组的消费实例,会负载消费消息。
-        props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
-        return KafkaSource.<String>builder()
-                .setProperties(props)
-                .setBootstrapServers(servers)
-                .setTopics(topic)
-                .setGroupId(groupId)
-                .setStartingOffsets(OffsetsInitializer.committedOffsets(OffsetResetStrategy.EARLIEST))
-                .setValueOnlyDeserializer(new SimpleStringSchema())
-                .build();
-    }
-
-    /**
-     * 公网
-     *
-     * @param servers
-     * @param username
-     * @param password
-     * @param sslPath
-     * @param topic
-     * @param groupId
-     * @return
-     */
-    private static KafkaSource<String> buildKafkaSource(String servers, String username, String password, String sslPath, String topic, String groupId) {
-        Properties props = new Properties();
-        props.put(SaslConfigs.SASL_JAAS_CONFIG, "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"" + username + "\" password=\"" + password + "\";");
-        props.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, sslPath);
-        props.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "KafkaOnsClient");
-        props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
-        props.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
-        props.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
-        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
-        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
-        return KafkaSource.<String>builder()
-                .setBootstrapServers(servers)
-                .setTopics(topic)
-                .setGroupId(groupId)
-                .setProperties(props)
-                .setStartingOffsets(OffsetsInitializer.committedOffsets(OffsetResetStrategy.EARLIEST))
-                .setValueOnlyDeserializer(new SimpleStringSchema())
-                .build();
-    }
 }

+ 111 - 339
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/PlanStatJob.java

@@ -9,24 +9,28 @@ import com.aliyun.odps.task.SQLTask;
 import com.tencent.ads.model.DailyReportsGetListStruct;
 import com.tencent.ads.model.HourlyReportsGetListStruct;
 import flink.zanxiangnet.ad.monitoring.maxcompute.MaxComputeLog;
-import flink.zanxiangnet.ad.monitoring.maxcompute.sink.TunnelBatchSink;
 import flink.zanxiangnet.ad.monitoring.pojo.dto.AdDataOfDayDTO;
 import flink.zanxiangnet.ad.monitoring.pojo.dto.AdDataOfMinuteDTO;
 import flink.zanxiangnet.ad.monitoring.pojo.dto.AdStatOfDayODSDTO;
 import flink.zanxiangnet.ad.monitoring.pojo.entity.*;
 import flink.zanxiangnet.ad.monitoring.pojo.properties.ApplicationProperties;
-import flink.zanxiangnet.ad.monitoring.pojo.properties.KafkaProperties;
+import flink.zanxiangnet.ad.monitoring.process.PlanMinuteDWDProcess;
+import flink.zanxiangnet.ad.monitoring.sink.TunnelBatchStreamSink;
+import flink.zanxiangnet.ad.monitoring.stream.KeyedBatchStream;
+import flink.zanxiangnet.ad.monitoring.trigger.PlanMinuteODSStreamTrigger;
 import flink.zanxiangnet.ad.monitoring.util.DateUtil;
 import flink.zanxiangnet.ad.monitoring.util.JsonUtil;
+import flink.zanxiangnet.ad.monitoring.kafka.KafkaComponent;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
 import org.apache.flink.api.common.eventtime.WatermarkStrategy;
-import org.apache.flink.api.common.serialization.SimpleStringSchema;
-import org.apache.flink.api.java.functions.KeySelector;
+import org.apache.flink.api.common.state.MapState;
+import org.apache.flink.api.common.state.MapStateDescriptor;
+import org.apache.flink.api.common.state.ValueState;
+import org.apache.flink.api.common.state.ValueStateDescriptor;
+import org.apache.flink.api.common.typeinfo.Types;
 import org.apache.flink.configuration.Configuration;
 import org.apache.flink.connector.kafka.source.KafkaSource;
-import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer;
-import org.apache.flink.streaming.api.datastream.DataStream;
 import org.apache.flink.streaming.api.datastream.DataStreamSource;
 import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
 import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
@@ -35,22 +39,15 @@ import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
 import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
 import org.apache.flink.streaming.api.windowing.time.Time;
 import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
-import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
 import org.apache.flink.util.Collector;
 import org.apache.flink.util.OutputTag;
-import org.apache.kafka.clients.CommonClientConfigs;
-import org.apache.kafka.clients.consumer.ConsumerConfig;
-import org.apache.kafka.clients.consumer.OffsetResetStrategy;
-import org.apache.kafka.clients.producer.ProducerConfig;
-import org.apache.kafka.common.config.SaslConfigs;
-import org.apache.kafka.common.config.SslConfigs;
 import org.springframework.beans.BeanUtils;
 
 import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
 public class PlanStatJob {
@@ -76,11 +73,8 @@ public class PlanStatJob {
         });
         env.getConfig().setGlobalJobParameters(configuration);
 
-        Properties adStreamOfMinuteProps = new Properties();
-        adStreamOfMinuteProps.load(PlanStatJob.class.getResourceAsStream("/ad_stream_of_minute.properties"));
-        KafkaSource<String> adStreamOfMinuteSource = buildKafkaSource(adStreamOfMinuteProps);
-
-        DataStreamSource<String> adStreamOfMinuteIn = env.fromSource(adStreamOfMinuteSource, WatermarkStrategy.noWatermarks(), "adStreamOfMinuteSource_kafka");
+        KafkaSource<String> adStreamOfMinuteSource = KafkaComponent.buildKafkaSource(props, KafkaComponent.KafkaTopic.adHourTopic, KafkaComponent.KafkaTopic.KafkaGroupId.planHourConsumerGroup);
+        DataStreamSource<String> adStreamOfMinuteIn = env.fromSource(adStreamOfMinuteSource, WatermarkStrategy.noWatermarks(), "planStreamOfMinuteSource_kafka");
 
         // 广告分钟数据(前 5分钟的广告消耗数据)
         final OutputTag<AdDataOfMinuteODS> adMinuteStreamTag = new OutputTag<AdDataOfMinuteODS>("adMinuteStream") {
@@ -126,46 +120,28 @@ public class PlanStatJob {
                 });
 
         // 分钟流
-        DataStream<AdDataOfMinuteODS> adMinuteODSStream = adODSStream.getSideOutput(adMinuteStreamTag);
-        // 写入原始表
-        adMinuteODSStream.addSink(new TunnelBatchSink<>(AdDataOfMinuteODS.class, 36000L, 64000L, 3));
-        adMinuteODSStream
+        SingleOutputStreamOperator<PlanStatOfMinuteDWD> planMinuteDWDStream = adODSStream.getSideOutput(adMinuteStreamTag)
                 // 打水印,允许数据延迟 6分钟,同时指定时间流
                 .assignTimestampsAndWatermarks(WatermarkStrategy.<AdDataOfMinuteODS>forBoundedOutOfOrderness(Duration.ofMinutes(6L))
                         .withTimestampAssigner((SerializableTimestampAssigner<AdDataOfMinuteODS>) (adODS, l) -> adODS.getStatTime()))
-                .keyBy(AdDataOfMinuteODS::getAdId)
+                .keyBy(AdDataOfMinuteODS::getCampaignId)
                 // 开一个 5分钟的滚动窗口
                 .window(TumblingEventTimeWindows.of(Time.minutes(5L)))
-                .process(new ProcessWindowFunction<AdDataOfMinuteODS, AdStatOfMinuteDWD, Long, TimeWindow>() {
-                    // 对象锁,防止MaxCompute的 Tunnel对象多次初始化
-                    private final String DUMMY_LOCK = "DUMMY_LOCK";
+                .trigger(new PlanMinuteODSStreamTrigger())
+                .process(new PlanMinuteDWDProcess());
+        // .addSink(new TunnelBatchSink<>(AdStatOfMinuteDWD.class, 30000L, 365L, 6));
+        new KeyedBatchStream<>("planMinuteDWDStream", planMinuteDWDStream.keyBy(PlanStatOfMinuteDWD::getStatDay), 4000L, 60 * 1000L)
+                .toBatch()
+                .addSink(new TunnelBatchStreamSink<>(PlanStatOfMinuteDWD.class));
+
+        // 小时流
+        SingleOutputStreamOperator<PlanStatOfHourDWD> planHourDWDStream = adODSStream.getSideOutput(adHourStreamTag).keyBy(AdDataOfHourODS::getCampaignId)
+                .countWindow(1).process(new ProcessWindowFunction<AdDataOfHourODS, PlanStatOfHourDWD, Long, GlobalWindow>() {
                     private Odps odps;
-                    private long lastClearTime = System.currentTimeMillis();
-                    private final Map<Long, String> lastAdDayQueryDay = new ConcurrentHashMap<>();
-                    // 历史的天数据
-                    private final Map<Long, AdStatOfDayDWD> historyDayData = new ConcurrentHashMap<>();
-                    // 之前聚合的昨天的数据
-                    private final Map<Long, Map<String, AdStatOfMinuteDWD>> historyMinuteData = new ConcurrentHashMap<>();
-                    // 前 5分钟聚合的数据
-                    private final Map<Long, Map<String, AdStatOfMinuteDWD>> lastReduceData = new ConcurrentHashMap<>();
-
-                    private void clearData() {
-                        lastAdDayQueryDay.entrySet().removeIf(entry -> DateUtil.parseLocalDate(entry.getValue()).compareTo(LocalDate.now().minusDays(3L)) <= 0);
-                        historyDayData.entrySet().removeIf(entry -> DateUtil.parseLocalDate(entry.getValue().getStatDay()).compareTo(LocalDate.now().minusDays(50L)) <= 0);
-                        historyMinuteData.forEach((key, value) -> {
-                            if (value != null) {
-                                value.entrySet().removeIf(entry2 -> DateUtil.parseLocalDate(entry2.getKey()).compareTo(LocalDate.now().minusDays(3L)) <= 0);
-                            }
-                        });
-                        historyMinuteData.entrySet().removeIf(entry -> entry.getValue() == null || entry.getValue().isEmpty());
-
-                        lastReduceData.forEach((key, value) -> {
-                            if (value != null) {
-                                value.entrySet().removeIf(entry -> DateUtil.parseOfMinuteReduce(entry.getKey()).compareTo(LocalDateTime.now().minusHours(1)) <= 0);
-                            }
-                        });
-                        lastReduceData.entrySet().removeIf(entry -> entry.getValue() == null || entry.getValue().isEmpty());
-                    }
+                    // 上次查询的天数据
+                    private ValueState<String> lastQueryDayState;
+                    // 聚合的天的数据
+                    private MapState<String, PlanStatOfHourDWD> historyReduceState;
 
                     @Override
                     public void open(Configuration conf) {
@@ -179,194 +155,57 @@ public class PlanStatJob {
                         odps.getRestClient().setRetryLogger(new MaxComputeLog());
                         odps.setEndpoint(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_ENDPOINT));
                         odps.setDefaultProject(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_PROJECT_NAME));
-                    }
-
-                    @Override
-                    public void process(Long elementCount, ProcessWindowFunction<AdDataOfMinuteODS, AdStatOfMinuteDWD, Long, TimeWindow>.Context context,
-                                        Iterable<AdDataOfMinuteODS> iterable, Collector<AdStatOfMinuteDWD> collector) throws Exception {
-                        LocalDateTime beginTime = DateUtil.milliToLocalDateTime(context.window().getStart());
-                        String statDay = DateUtil.formatLocalDate(beginTime.toLocalDate());
-                        String reduceTimeKey = "";
-                        int hour = beginTime.getHour();
-                        long now = System.currentTimeMillis();
-                        AdDataOfMinuteODS statODS = null;
-                        List<AdDataOfMinuteODS> adDataOfMinuteODSList = new ArrayList<>(24);
-                        for (AdDataOfMinuteODS adDataOfMinuteODS : iterable) {
-                            adDataOfMinuteODSList.add(adDataOfMinuteODS);
-                            if (adDataOfMinuteODS.getHour() != hour) {
-                                continue;
-                            }
-                            statODS = adDataOfMinuteODS;
-                            break;
-                        }
-                        if (statODS == null) {
-                            return;
-                        }
-
-                        // 每 12小时清理一次数据
-                        if (now - lastClearTime > 12 * 60 * 60 * 1000) {
-                            synchronized (DUMMY_LOCK) {
-                                if (now - lastClearTime > 12 * 60 * 60 * 1000) {
-                                    clearData();
-                                    lastClearTime = now;
-                                }
-                            }
-                        }
-                        Long adId = statODS.getAdId();
-
-                        // 昨天聚合的数据
-                        Map<String, AdStatOfMinuteDWD> historyMinuteMapping = historyMinuteData.computeIfAbsent(adId, key -> new HashMap<>());
-                        AdStatOfMinuteDWD yesterdayMinuteDWD = historyMinuteMapping.get(DateUtil.formatLocalDate(beginTime.minusDays(1L).toLocalDate()));
-                        // 之前的数据
-                        String lastQueryDay = lastAdDayQueryDay.get(adId);
-                        if (lastQueryDay == null || !lastQueryDay.equals(statDay)) {
-                            // 往前找 30天
-                            LocalDate endDay = beginTime.minusDays(2L).toLocalDate(), beginDay = endDay.minusDays(30L);
-                            Instance instance = SQLTask.run(odps, "SELECT * FROM ad_stat_of_day_dwd WHERE stat_day >= \"" + DateUtil.formatLocalDate(beginDay) + "\" AND stat_day <= \"" + endDay + "\" AND ad_id = " + adId + ";");
-                            instance.waitForSuccess();
-                            List<Record> records = SQLTask.getResult(instance);
-                            List<AdStatOfDayDWD> temp = records.stream().map(AdStatOfDayDWD::byMaxCompute).sorted((val1, val2) -> {
-                                if (val1.getStatDay().equals(val2.getStatDay())) {
-                                    return new Long(val1.getCreateTime().getTime() - val2.getCreateTime().getTime()).intValue();
-                                }
-                                return DateUtil.parseLocalDate(val1.getStatDay()).compareTo(DateUtil.parseLocalDate(val2.getStatDay()));
-                            }).collect(Collectors.toList());
-                            historyDayData.put(adId, temp.isEmpty() ? null : temp.get(temp.size() - 1));
-                            lastAdDayQueryDay.put(adId, statDay);
-                        }
-                        AdStatOfDayDWD beforeYesterdayDayDWD = historyDayData.get(adId);
-                        Map<String, AdStatOfMinuteDWD> lastReduceMapping = lastReduceData.computeIfAbsent(adId, key -> new HashMap<>());
-                        // 聚合当天的全部数据
-                        AdStatOfMinuteDWD nowAdStat = AdStatOfMinuteDWD.reduce(beforeYesterdayDayDWD, yesterdayMinuteDWD, adDataOfMinuteODSList, statODS, null, now);
-                        collector.collect(nowAdStat);
-                        lastReduceMapping.put(reduceTimeKey, nowAdStat);
-                        historyMinuteMapping.put(statDay, nowAdStat);
-                    }
-                }).addSink(new TunnelBatchSink<>(AdStatOfMinuteDWD.class, 30000L, 365L, 6));
-        ;
-
-        // 小时流(直接写到小时报表的 ods)
-        DataStream<AdDataOfHourODS> adHourODSStream = adODSStream.getSideOutput(adHourStreamTag);
-        // 写入原始表
-        adHourODSStream.addSink(new TunnelBatchSink<>(AdDataOfHourODS.class, 36000L, 64000L, 10));
-        adHourODSStream.keyBy(AdDataOfHourODS::getAdId)
-                .countWindow(1).process(new ProcessWindowFunction<AdDataOfHourODS, AdStatOfHourDWD, Long, GlobalWindow>() {
-                    private Odps odps;
-                    private final Map<Long, Long> lastAdQueryTime = new ConcurrentHashMap<>();
-                    private final Map<Long, Map<String, Map<Integer, AdStatOfHourDWD>>> historyAdData = new ConcurrentHashMap<>();
 
-                    @Override
-                    public void open(Configuration conf) {
-                        Map<String, String> params = getRuntimeContext()
-                                .getExecutionConfig()
-                                .getGlobalJobParameters()
-                                .toMap();
-                        Account account = new AliyunAccount(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_ID),
-                                params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_KEY));
-                        odps = new Odps(account);
-                        odps.getRestClient().setRetryLogger(new MaxComputeLog());
-                        odps.setEndpoint(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_ENDPOINT));
-                        odps.setDefaultProject(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_PROJECT_NAME));
+                        lastQueryDayState = getRuntimeContext().getState(new ValueStateDescriptor<>("lastQueryDayState", String.class));
+                        historyReduceState = getRuntimeContext().getMapState(new MapStateDescriptor<>("historyReduceState", Types.STRING, Types.POJO(PlanStatOfHourDWD.class)));
                     }
 
                     @Override
-                    public void process(Long elementsCount, ProcessWindowFunction<AdDataOfHourODS, AdStatOfHourDWD, Long, GlobalWindow>.Context context, Iterable<AdDataOfHourODS> iterable, Collector<AdStatOfHourDWD> collector) throws Exception {
+                    public void process(Long elementsCount, ProcessWindowFunction<AdDataOfHourODS, PlanStatOfHourDWD, Long, GlobalWindow>.Context context,
+                                        Iterable<AdDataOfHourODS> iterable, Collector<PlanStatOfHourDWD> collector) throws Exception {
                         AdDataOfHourODS element = iterable.iterator().next();
                         LocalDate statDay = DateUtil.parseLocalDate(element.getStatDay());
-                        Integer hour = element.getHour();
+                        LocalDateTime statTime = LocalDateTime.of(statDay, LocalTime.of(element.getHour(), 0, 0));
                         long now = System.currentTimeMillis();
-                        Long adId = element.getAdId();
+                        LocalDate today = LocalDate.now();
 
-                        Map<String, Map<Integer, AdStatOfHourDWD>> historyData = historyAdData.computeIfAbsent(adId, key -> new HashMap<>());
-                        Long lastQueryTime = lastAdQueryTime.get(adId);
+                        String lastQueryDay = lastQueryDayState.value();
                         // 从 maxCompute拉取指定 广告的历史数据
-                        if (lastQueryTime == null || (now - lastQueryTime) > 3 * 60 * 60 * 1000) {
-                            LocalDate endTime = LocalDate.now(), beginTime = statDay.minusDays(40);
-                            Instance instance = SQLTask.run(odps, "SELECT * FROM ad_stat_of_hour_dwd WHERE stat_day >= \"" + DateUtil.formatLocalDate(beginTime) + "\" AND stat_day <= \"" + DateUtil.formatLocalDate(endTime) + "\" AND ad_id = " + element.getAdId() + ";");
+                        if (lastQueryDay == null || !lastQueryDay.equals(DateUtil.formatLocalDate(today))) {
+                            LocalDate endDay = today, beginDay = statDay.minusDays(60);
+                            Instance instance = SQLTask.run(odps, "SELECT * FROM plan_stat_of_hour_dwd WHERE stat_day >= \"" + DateUtil.formatLocalDate(beginDay) + "\" AND stat_day <= \"" + DateUtil.formatLocalDate(endDay) + "\" AND campaign_id = " + element.getCampaignId() + ";");
                             instance.waitForSuccess();
                             List<Record> records = SQLTask.getResult(instance);
-                            List<AdStatOfHourDWD> historyList = records.stream()
-                                    .map(AdStatOfHourDWD::byMaxCompute)
+                            Map<String, PlanStatOfHourDWD> historyHourMap = records.stream()
+                                    .map(PlanStatOfHourDWD::byMaxCompute)
                                     .sorted((o1, o2) -> new Long(o1.getCreateTime().getTime() - o2.getCreateTime().getTime()).intValue())
-                                    .collect(Collectors.toList());
-                            for (AdStatOfHourDWD adStatOfHourDWD : historyList) {
-                                Map<Integer, AdStatOfHourDWD> hourMapping = historyData.computeIfAbsent(adStatOfHourDWD.getStatDay(), key -> new HashMap<>());
-                                hourMapping.put(adStatOfHourDWD.getHour(), adStatOfHourDWD);
-                            }
-                            lastAdQueryTime.put(adId, now);
+                                    .collect(Collectors.toMap(data -> data.getStatDay() + data.getHour(), data -> data, (val1, val2) -> val2));
+                            historyReduceState.clear();
+                            historyReduceState.putAll(historyHourMap);
+                            lastQueryDayState.update(DateUtil.formatLocalDate(today));
                         }
-                        // 找到这个号当天内上个小时统计的数据
-                        AdStatOfHourDWD lastHourData = null;
-                        // 往前查 40天,找到广告上一次聚合的数据
-                        for (int i = 0; i < 40; i++) {
-                            Map<Integer, AdStatOfHourDWD> mapData = historyData.get(DateUtil.formatLocalDate(statDay.minusDays(i)));
-                            if (mapData == null || mapData.isEmpty()) {
-                                continue;
-                            }
-                            for (Map.Entry<Integer, AdStatOfHourDWD> temp : mapData.entrySet()) {
-                                if (temp.getKey() >= hour) {
-                                    continue;
-                                }
-                                if (lastHourData != null && lastHourData.getHour() >= temp.getKey()) {
-                                    continue;
-                                }
-                                lastHourData = temp.getValue();
-                            }
-                            if (lastHourData != null) {
+                        PlanStatOfHourDWD lastReduceData = null;
+                        for (int i = 1; i < 60 * 24; i++) {
+                            LocalDateTime time = statTime.minusHours(i);
+                            lastReduceData = historyReduceState.get(DateUtil.formatLocalDate(time.toLocalDate()) + time.getHour());
+                            if (lastReduceData != null) {
                                 break;
                             }
                         }
-                        AdStatOfHourDWD newStatData = AdStatOfHourDWD.reduce(lastHourData, element, now);
-                        Map<Integer, AdStatOfHourDWD> hourDataMapping = historyData.computeIfAbsent(newStatData.getStatDay(), key -> new HashMap<>());
-                        hourDataMapping.put(newStatData.getHour(), newStatData);
-                        collector.collect(newStatData);
-                    }
-                }).addSink(new TunnelBatchSink<>(AdStatOfHourDWD.class, 30000L, 365L, 6));
 
-        // 获取指定广告的历史统计信息(用于统计总的消耗信息等)
-        /*SingleOutputStreamOperator<AdStatOfHourDWD> adHourDWDStream = adHourODSStream.map(AdStatOfHourDWD::byODS)
-                .keyBy((KeySelector<AdStatOfHourDWD, String>) adHourDWD -> adHourDWD.getStatDay() + "_" + adHourDWD.getAdId())
-                // 开一个按天滚动的窗口
-                .window(TumblingEventTimeWindows.of(Time.days(1L)))
-                // 数据聚合
-                .reduce(new RichReduceFunction<AdStatOfHourDWD>() {
-                    // 初始化 MaxCompute连接实例
-                    private Odps odps;
-
-                    @Override
-                    public void open(Configuration parameters) throws Exception {
-                        Map<String, String> params = getRuntimeContext()
-                                .getExecutionConfig()
-                                .getGlobalJobParameters()
-                                .toMap();
-                        Account account = new AliyunAccount(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_ID),
-                                params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_KEY));
-                        Odps odps = new Odps(account);
-                        odps.getRestClient().setRetryLogger(new MaxComputeLog());
-                        odps.setEndpoint(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_ENDPOINT));
-                        odps.setDefaultProject(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_PROJECT_NAME));
-                    }
-
-                    @Override
-                    public AdStatOfHourDWD reduce(AdStatOfHourDWD adStatOfHourDWD, AdStatOfHourDWD t1) {
-                        Long accountId = adStatOfHourDWD.getAccountId();
-                        Long campaignId = adStatOfHourDWD.getCampaignId();
-                        Long adId = adStatOfHourDWD.getAdId();
-                        String statDay = adStatOfHourDWD.getStatDay();
-                        // 1、从 MaxCompute取出小时数据往前的所有无重复按天统计的数据,并聚合用来算总的
-                        // 2、当前流的所有数据聚合在一起作为天数据
-                        // 3、写出到 MaxCompute
-                        return null;
+                        PlanStatOfHourDWD newStatData = PlanStatOfHourDWD.reduce(lastReduceData, element, now);
+                        collector.collect(newStatData);
                     }
-                });*/
-
+                });
+        //.addSink(new TunnelBatchSink<>(AdStatOfHourDWD.class, 30000L, 365L, 6));
+        new KeyedBatchStream<>("planHourDWDStream", planHourDWDStream.keyBy(PlanStatOfHourDWD::getStatDay), 4000L, 60 * 1000L)
+                .toBatch()
+                .addSink(new TunnelBatchStreamSink<>(PlanStatOfHourDWD.class));
 
         // ------------------------------------------------------- 处理广告的天数据 -----------------------------------------
-        Properties adStreamOfDayProps = new Properties();
-        adStreamOfDayProps.load(PlanStatJob.class.getResourceAsStream("/ad_stream_of_day.properties"));
-        KafkaSource<String> adStreamOfDaySource = buildKafkaSource(adStreamOfDayProps);
+        KafkaSource<String> adStreamOfDaySource = KafkaComponent.buildKafkaSource(props, KafkaComponent.KafkaTopic.adDayTopic, KafkaComponent.KafkaTopic.KafkaGroupId.planDayConsumerGroup);
 
-        DataStreamSource<String> adStreamOfDayIn = env.fromSource(adStreamOfDaySource, WatermarkStrategy.noWatermarks(), "adStreamOfMinuteSource_kafka");
+        DataStreamSource<String> adStreamOfDayIn = env.fromSource(adStreamOfDaySource, WatermarkStrategy.noWatermarks(), "planStreamOfMinuteSource_kafka");
 
         // 广告日数据。往前回滚 10天
         final OutputTag<AdDataOfDayODS> adDayStreamRollDayTag = new OutputTag<AdDataOfDayODS>("adDayStreamRollDayTag") {
@@ -395,7 +234,6 @@ public class PlanStatJob {
                             .adDataOfDayODS(adODS)
                             .build();
                 });
-        adDayODSStream.map(AdStatOfDayODSDTO::getAdDataOfDayODS).addSink(new TunnelBatchSink<>(AdDataOfDayODS.class, 36000L, 6000L, 10));
 
         // 拆分流
         SingleOutputStreamOperator<AdStatOfDayODSDTO> adDayODSStreamSplit = adDayODSStream.process(new ProcessFunction<AdStatOfDayODSDTO, AdStatOfDayODSDTO>() {
@@ -414,15 +252,14 @@ public class PlanStatJob {
             }
         });
 
-        adDayODSStreamSplit.getSideOutput(adDayStreamRollDayTag)
-                // 打水印,允许延迟 10天(应为允许回滚 10天),同时指定事件时间
-                .assignTimestampsAndWatermarks(WatermarkStrategy.<AdDataOfDayODS>forBoundedOutOfOrderness(Duration.ofDays(10L))
-                        .withTimestampAssigner((SerializableTimestampAssigner<AdDataOfDayODS>) (adDay, l) -> DateUtil.localDateToMilli(DateUtil.parseLocalDate(adDay.getStatDay())))
-                ).keyBy(AdDataOfDayODS::getAdId)
-                .countWindow(1).process(new ProcessWindowFunction<AdDataOfDayODS, AdStatOfDayDWD, Long, GlobalWindow>() {
+        SingleOutputStreamOperator<PlanStatOfDayDWD> planDayDWDStream = adDayODSStreamSplit.getSideOutput(adDayStreamRollDayTag)
+                .keyBy(AdDataOfDayODS::getCampaignId)
+                .countWindow(1).process(new ProcessWindowFunction<AdDataOfDayODS, PlanStatOfDayDWD, Long, GlobalWindow>() {
                     private Odps odps;
-                    private final Map<Long, Long> lastAdQueryTime = new ConcurrentHashMap<>();
-                    private final Map<Long, Map<String, AdStatOfDayDWD>> historyAdData = new ConcurrentHashMap<>();
+                    // 上次查询的时间
+                    private ValueState<String> lastQueryDayState;
+                    // 之前聚合的昨天的数据
+                    private MapState<String, PlanStatOfDayDWD> historyReduceState;
 
                     @Override
                     public void open(Configuration conf) {
@@ -436,139 +273,74 @@ public class PlanStatJob {
                         odps.getRestClient().setRetryLogger(new MaxComputeLog());
                         odps.setEndpoint(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_ENDPOINT));
                         odps.setDefaultProject(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_PROJECT_NAME));
+
+                        lastQueryDayState = getRuntimeContext().getState(new ValueStateDescriptor<>("lastQueryDayState", String.class));
+                        historyReduceState = getRuntimeContext().getMapState(new MapStateDescriptor<>("historyReduceState", String.class, PlanStatOfDayDWD.class));
                     }
 
                     @Override
-                    public void process(Long elementsCount, ProcessWindowFunction<AdDataOfDayODS, AdStatOfDayDWD, Long, GlobalWindow>.Context context, Iterable<AdDataOfDayODS> iterable, Collector<AdStatOfDayDWD> collector) throws Exception {
+                    public void process(Long elementsCount, ProcessWindowFunction<AdDataOfDayODS, PlanStatOfDayDWD, Long, GlobalWindow>.Context context,
+                                        Iterable<AdDataOfDayODS> iterable, Collector<PlanStatOfDayDWD> collector) throws Exception {
                         AdDataOfDayODS element = iterable.iterator().next();
-                        Long adId = element.getAdId();
                         LocalDate statDay = DateUtil.parseLocalDate(element.getStatDay());
                         long now = System.currentTimeMillis();
 
-                        Map<String, AdStatOfDayDWD> historyData = historyAdData.computeIfAbsent(adId, key -> new HashMap<>());
-                        Long lastQueryTime = lastAdQueryTime.get(adId);
+                        String lastQueryDay = lastQueryDayState.value();
                         // 从 maxCompute查找广告的历史数据
-                        if (lastQueryTime == null || (now - lastQueryTime) > 60 * 60 * 1000) {
+                        if (lastQueryDay == null || !lastQueryDay.equals(DateUtil.formatLocalDate(LocalDate.now()))) {
                             LocalDate endTime = LocalDate.now(), beginTime = statDay.minusDays(60);
-                            Instance instance = SQLTask.run(odps, "SELECT * FROM ad_stat_of_day_dwd WHERE stat_day >= \"" + DateUtil.formatLocalDate(beginTime) + "\" AND stat_day <= \"" + DateUtil.formatLocalDate(endTime) + "\" AND ad_id = " + element.getAdId() + ";");
+                            Instance instance = SQLTask.run(odps, "SELECT * FROM plan_stat_of_day_dwd WHERE stat_day >= \"" + DateUtil.formatLocalDate(beginTime) + "\" AND stat_day <= \"" + DateUtil.formatLocalDate(endTime) + "\" AND campaign_id = " + element.getCampaignId() + ";");
                             instance.waitForSuccess();
                             List<Record> records = SQLTask.getResult(instance);
-                            List<AdStatOfDayDWD> list = records.stream()
-                                    .map(AdStatOfDayDWD::byMaxCompute)
-                                    .sorted((o1, o2) -> new Long(o1.getCreateTime().getTime() - o2.getCreateTime().getTime()).intValue()).collect(Collectors.toList());
-                            for (AdStatOfDayDWD adStatOfDayDWD : list) {
-                                historyData.put(adStatOfDayDWD.getStatDay(), adStatOfDayDWD);
-                            }
-                            lastAdQueryTime.put(adId, now);
+                            Map<String, PlanStatOfDayDWD> historyData = records.stream()
+                                    .map(PlanStatOfDayDWD::byMaxCompute)
+                                    .sorted((o1, o2) -> new Long(o1.getCreateTime().getTime() - o2.getCreateTime().getTime()).intValue())
+                                    .collect(Collectors.toMap(PlanStatOfDayDWD::getStatDay, data -> data, (val1, val2) -> val2));
+                            historyReduceState.clear();
+                            historyReduceState.putAll(historyData);
+                            lastQueryDayState.update(DateUtil.formatLocalDate(LocalDate.now()));
                         }
-                        AdStatOfDayDWD newStatData = null;
+                        PlanStatOfDayDWD lastReduceData = null;
                         for (int i = 1; i <= 60; i++) {
-                            AdStatOfDayDWD oldStatData = historyData.get(DateUtil.formatLocalDate(statDay.minusDays(i)));
-                            if (oldStatData == null) {
-                                continue;
+                            lastReduceData = historyReduceState.get(DateUtil.formatLocalDate(statDay.minusDays(i)));
+                            if (lastReduceData != null) {
+                                break;
                             }
-                            newStatData = AdStatOfDayDWD.reduce(oldStatData, element, now);
-                            break;
-                        }
-                        if (newStatData == null) {
-                            newStatData = AdStatOfDayDWD.reduce(null, element, now);
                         }
-                        historyData.put(newStatData.getStatDay(), newStatData);
+                        PlanStatOfDayDWD newStatData = PlanStatOfDayDWD.reduce(lastReduceData, element, now);
+                        historyReduceState.put(DateUtil.formatLocalDate(statDay), newStatData);
                         collector.collect(newStatData);
                     }
-                }).addSink(new TunnelBatchSink<>(AdStatOfDayDWD.class, 30000L, 365L, 6));
-
-        adDayODSStreamSplit.getSideOutput(adDayStreamRollYearTag)
-                .keyBy((KeySelector<AdDataOfDayODS, Long>) AdDataOfDayODS::getAdId)
-                .countWindow(1).process(new ProcessWindowFunction<AdDataOfDayODS, AdStatOfDayDWD, Long, GlobalWindow>() {
+                });
+        //.addSink(new TunnelBatchSink<>(AdStatOfDayDWD.class, 30000L, 365L, 6));
+        new KeyedBatchStream<>("planDayDWDStream", planDayDWDStream.keyBy(PlanStatOfDayDWD::getStatDay), 4000L, 60 * 1000L)
+                .toBatch()
+                .addSink(new TunnelBatchStreamSink<>(PlanStatOfDayDWD.class));
+
+        SingleOutputStreamOperator<PlanStatOfDayDWD> planDayDWDYearStream = adDayODSStreamSplit.getSideOutput(adDayStreamRollYearTag)
+                .keyBy(AdDataOfDayODS::getCampaignId)
+                .countWindow(1).process(new ProcessWindowFunction<AdDataOfDayODS, PlanStatOfDayDWD, Long, GlobalWindow>() {
                     // 上次聚合的结果
-                    private AdStatOfDayDWD lastReduce;
+                    private ValueState<PlanStatOfDayDWD> lastReduceState;
+
+                    public void open(Configuration conf) {
+                        lastReduceState = getRuntimeContext().getState(new ValueStateDescriptor<>("lastReduceState", PlanStatOfDayDWD.class));
+                    }
 
                     @Override
-                    public void process(Long elementsCount, ProcessWindowFunction<AdDataOfDayODS, AdStatOfDayDWD, Long, GlobalWindow>.Context context, Iterable<AdDataOfDayODS> elements, Collector<AdStatOfDayDWD> out) {
+                    public void process(Long elementsCount, ProcessWindowFunction<AdDataOfDayODS, PlanStatOfDayDWD, Long, GlobalWindow>.Context context,
+                                        Iterable<AdDataOfDayODS> elements, Collector<PlanStatOfDayDWD> out) throws Exception {
                         AdDataOfDayODS element = elements.iterator().next();
-                        lastReduce = AdStatOfDayDWD.reduce(lastReduce, element, System.currentTimeMillis());
-                        out.collect(lastReduce);
+                        PlanStatOfDayDWD newStatDWD = PlanStatOfDayDWD.reduce(lastReduceState.value(), element, System.currentTimeMillis());
+                        out.collect(newStatDWD);
+                        lastReduceState.update(newStatDWD);
                     }
-                }).addSink(new TunnelBatchSink<>(AdStatOfDayDWD.class, 30000L, 365L, 6));
+                });
+        //.addSink(new TunnelBatchSink<>(AdStatOfDayDWD.class, 30000L, 365L, 6));
+        new KeyedBatchStream<>("planDayDWDYearStream", planDayDWDYearStream.keyBy(PlanStatOfDayDWD::getStatDay), 4000L, 60 * 1000L)
+                .toBatch()
+                .addSink(new TunnelBatchStreamSink<>(PlanStatOfDayDWD.class));
 
         env.execute();
     }
-
-    private static KafkaSource<String> buildKafkaSource(Properties props) {
-        return buildKafkaSource(props.getProperty(KafkaProperties.KAFKA_SERVERS),
-                props.getProperty(KafkaProperties.KAFKA_USERNAME),
-                props.getProperty(KafkaProperties.KAFKA_PASSWORD),
-                props.getProperty(KafkaProperties.KAFKA_SSL_PATH),
-                props.getProperty(KafkaProperties.KAFKA_TOPIC),
-                props.getProperty(KafkaProperties.KAFKA_GROUP_ID)
-        );
-    }
-
-    /**
-     * 内网
-     *
-     * @param servers
-     * @param username
-     * @param password
-     * @param sslPath
-     * @param topic
-     * @param groupId
-     * @return
-     */
-    private static KafkaSource<String> buildKafkaSourceVPC(String servers, String username, String password, String sslPath, String topic, String groupId) {
-        Properties props = new Properties();
-        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
-        //两次Poll之间的最大允许间隔。
-        //消费者超过该值没有返回心跳,服务端判断消费者处于非存活状态,服务端将消费者从Group移除并触发Rebalance,默认30s。
-        props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 30000);
-        //每次Poll的最大数量。
-        //注意该值不要改得太大,如果Poll太多数据,而不能在下次Poll之前消费完,则会触发一次负载均衡,产生卡顿。
-        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 30);
-        //消息的反序列化方式。
-        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
-        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
-        //当前消费实例所属的消费组,请在控制台申请之后填写。
-        //属于同一个组的消费实例,会负载消费消息。
-        props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
-        return KafkaSource.<String>builder()
-                .setProperties(props)
-                .setBootstrapServers(servers)
-                .setTopics(topic)
-                .setGroupId(groupId)
-                .setStartingOffsets(OffsetsInitializer.committedOffsets(OffsetResetStrategy.EARLIEST))
-                .setValueOnlyDeserializer(new SimpleStringSchema())
-                .build();
-    }
-
-    /**
-     * 公网
-     *
-     * @param servers
-     * @param username
-     * @param password
-     * @param sslPath
-     * @param topic
-     * @param groupId
-     * @return
-     */
-    private static KafkaSource<String> buildKafkaSource(String servers, String username, String password, String sslPath, String topic, String groupId) {
-        Properties props = new Properties();
-        props.put(SaslConfigs.SASL_JAAS_CONFIG, "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"" + username + "\" password=\"" + password + "\";");
-        props.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, sslPath);
-        props.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "KafkaOnsClient");
-        props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
-        props.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
-        props.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
-        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
-        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
-        return KafkaSource.<String>builder()
-                .setBootstrapServers(servers)
-                .setTopics(topic)
-                .setGroupId(groupId)
-                .setProperties(props)
-                .setStartingOffsets(OffsetsInitializer.committedOffsets(OffsetResetStrategy.EARLIEST))
-                .setValueOnlyDeserializer(new SimpleStringSchema())
-                .build();
-    }
 }

+ 52 - 0
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/kafka/KafkaComponent.java

@@ -0,0 +1,52 @@
+package flink.zanxiangnet.ad.monitoring.kafka;
+
+import org.apache.flink.api.common.serialization.SimpleStringSchema;
+import org.apache.flink.connector.kafka.source.KafkaSource;
+import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer;
+import org.apache.kafka.clients.CommonClientConfigs;
+import org.apache.kafka.clients.consumer.OffsetResetStrategy;
+import org.apache.kafka.common.config.SaslConfigs;
+import org.apache.kafka.common.config.SslConfigs;
+
+import java.util.Properties;
+
+public class KafkaComponent {
+    public static class KafkaTopic {
+        public static final String adHourTopic = "ad_cost_topic";
+        public static final String adDayTopic = "ad_day_cost_topic";
+
+        public static class KafkaGroupId {
+            public static final String adHourConsumerGroup = "ad_hour_consumer_group";
+            public static final String adDayConsumerGroup = "ad_day_consumer_group";
+
+            public static final String planHourConsumerGroup = "plan_hour_consumer_group";
+            public static final String planDayConsumerGroup = "plan_day_consumer_group";
+        }
+    }
+
+    public static KafkaSource<String> buildKafkaSource(Properties props, String topic, String groupId) {
+        String connModule = props.getProperty(KafkaProperties.KAFKA_MODULE);
+        Properties kafkaProps = new Properties();
+        if (connModule.equals(KafkaProperties.KAFKA_MODULE_SASL_SSL)) {
+            System.setProperty("javax.net.ssl.trustStore", props.getProperty(KafkaProperties.KAFKA_SSL_PATH));
+            System.setProperty("javax.net.ssl.trustStorePassword", "KafkaOnsClient");
+
+            kafkaProps.put(SaslConfigs.SASL_JAAS_CONFIG, "org.apache.kafka.common.security.plain.PlainLoginModule required username=\""
+                    + props.getProperty(KafkaProperties.KAFKA_USERNAME)
+                    + "\" password=\"" + props.getProperty(KafkaProperties.KAFKA_PASSWORD) + "\";");
+            kafkaProps.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, props.getProperty(KafkaProperties.KAFKA_SSL_PATH));
+            kafkaProps.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "KafkaOnsClient");
+            kafkaProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
+            kafkaProps.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
+            kafkaProps.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
+        }
+        return KafkaSource.<String>builder()
+                .setBootstrapServers(props.getProperty(KafkaProperties.KAFKA_SERVERS))
+                .setTopics(topic)
+                .setGroupId(groupId)
+                .setProperties(kafkaProps)
+                .setStartingOffsets(OffsetsInitializer.committedOffsets(OffsetResetStrategy.EARLIEST))
+                .setValueOnlyDeserializer(new SimpleStringSchema())
+                .build();
+    }
+}

+ 18 - 0
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/kafka/KafkaProperties.java

@@ -0,0 +1,18 @@
+package flink.zanxiangnet.ad.monitoring.kafka;
+
+public class KafkaProperties {
+    /**
+     * 公网模式
+     */
+    public static final String KAFKA_MODULE_SASL_SSL = "SASL_SSL";
+    /**
+     * 内网模式
+     */
+    public static final String KAFKA_MODULE_VPC = "VPC";
+
+    public static final String KAFKA_SERVERS = "kafka.servers";
+    public static final String KAFKA_USERNAME = "kafka.username";
+    public static final String KAFKA_PASSWORD = "kafka.password";
+    public static final String KAFKA_SSL_PATH = "kafka.sslPath";
+    public static final String KAFKA_MODULE = "kafka.connModule";
+}

+ 4 - 11
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/pojo/entity/PlanStatOfDayDWD.java

@@ -4,20 +4,13 @@ import com.aliyun.odps.data.Record;
 import com.google.gson.annotations.SerializedName;
 import flink.zanxiangnet.ad.monitoring.maxcompute.bean.annotation.MaxComputeColumn;
 import flink.zanxiangnet.ad.monitoring.maxcompute.bean.annotation.MaxComputeTable;
-import flink.zanxiangnet.ad.monitoring.util.JsonUtil;
 import flink.zanxiangnet.ad.monitoring.util.ObjectUtil;
 import flink.zanxiangnet.ad.monitoring.util.PlanUtil;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Data;
-import lombok.NoArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 
 import java.io.Serializable;
 import java.util.*;
-import java.util.function.Function;
-import java.util.stream.Collectors;
 
 @Data
 @MaxComputeTable("plan_stat_of_day_dwd")
@@ -66,7 +59,7 @@ public class PlanStatOfDayDWD implements Serializable {
      */
     @MaxComputeColumn(ignore = true)
     @SerializedName("ad_group_map")
-    private Map<Long, List<Long>> adGroupMap;
+    private Map<Long, Set<Long>> adGroupMap;
 
     /**
      * 广告组与广告的映射关系
@@ -982,8 +975,8 @@ public class PlanStatOfDayDWD implements Serializable {
         BeanUtils.copyProperties(value2, result);
         result.setCreateTime(new Date(createTime));
         if (value1 == null) {
-            Map<Long, List<Long>> adGroupMap = new HashMap<>();
-            List<Long> adIdsTemp = new ArrayList<>(3);
+            Map<Long, Set<Long>> adGroupMap = new HashMap<>();
+            Set<Long> adIdsTemp = new HashSet<>(3);
             adIdsTemp.add(value2.getAdId());
             adGroupMap.put(value2.getAdgroupId(), adIdsTemp);
             result.setAdGroupMap(adGroupMap);
@@ -1141,7 +1134,7 @@ public class PlanStatOfDayDWD implements Serializable {
             result.setNoInterestCountTotal(value2.getNoInterestCount());
             result.setNoInterestCountDay(value2.getNoInterestCount());
         } else {
-            value1.getAdGroupMap().computeIfAbsent(value2.getAdgroupId(), key -> new ArrayList<>(3)).add(value2.getAdId());
+            value1.getAdGroupMap().computeIfAbsent(value2.getAdgroupId(), key -> new HashSet<>(3)).add(value2.getAdId());
             value1.setAdGroupMapStr(PlanUtil.adGroupMapStr(value1.getAdGroupMap()));
             value1.getAdIds().add(value2.getAdId());
             value1.setAdIdsStr(PlanUtil.adIdsStr(value1.getAdIds()));

+ 46 - 87
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/pojo/entity/PlanStatOfHourDWD.java

@@ -5,16 +5,17 @@ import com.google.gson.annotations.SerializedName;
 import flink.zanxiangnet.ad.monitoring.maxcompute.bean.annotation.MaxComputeColumn;
 import flink.zanxiangnet.ad.monitoring.maxcompute.bean.annotation.MaxComputeTable;
 import flink.zanxiangnet.ad.monitoring.util.ObjectUtil;
+import flink.zanxiangnet.ad.monitoring.util.PlanUtil;
 import lombok.Data;
 import org.springframework.beans.BeanUtils;
 
-import java.util.Date;
+import java.util.*;
 
 /**
  * 广告维度的小时统计数据
  */
 @Data
-@MaxComputeTable("ad_stat_of_hour_dwd")
+@MaxComputeTable("plan_stat_of_hour_dwd")
 public class PlanStatOfHourDWD {
 
     /**
@@ -61,16 +62,30 @@ public class PlanStatOfHourDWD {
     private String wechatAgencyId;
 
     /**
-     * 广告组 id
+     * 广告组与广告的映射关系
      */
-    @SerializedName("adgroup_id")
-    private Long adgroupId;
+    @MaxComputeColumn(ignore = true)
+    @SerializedName("ad_group_map")
+    private Map<Long, Set<Long>> adGroupMap;
 
     /**
-     * 广告 id
+     * 广告组与广告的映射关系
      */
-    @SerializedName("ad_id")
-    private Long adId;
+    @SerializedName("ad_group_map_str")
+    private String adGroupMapStr;
+
+    /**
+     * 广告 id列表
+     */
+    @MaxComputeColumn(ignore = true)
+    @SerializedName("ad_ids")
+    private Set<Long> adIds;
+
+    /**
+     * 广告 id列表
+     */
+    @SerializedName("ad_ids_str")
+    private String adIdsStr;
 
     /**
      * 记录创建时间
@@ -1200,88 +1215,23 @@ public class PlanStatOfHourDWD {
     @SerializedName("no_interest_count_hour")
     private Long noInterestCountHour;
 
-    public static PlanStatOfHourDWD byODS(AdDataOfHourODS adODS) {
-        PlanStatOfHourDWD adStatOfHour = new PlanStatOfHourDWD();
-        adStatOfHour.setStatDay(adODS.getStatDay());
-        adStatOfHour.setHour(adODS.getHour());
-        adStatOfHour.setAccountId(adODS.getAccountId());
-        adStatOfHour.setCampaignId(adODS.getCampaignId());
-        adStatOfHour.setAgencyAccountId(adODS.getAgencyAccountId());
-        adStatOfHour.setWechatAccountId(adODS.getWechatAccountId());
-        adStatOfHour.setWechatAgencyId(adODS.getWechatAgencyId());
-        adStatOfHour.setAdgroupId(adODS.getAdgroupId());
-        adStatOfHour.setAdId(adODS.getAdId());
-        adStatOfHour.setCostDeviationRateHour(adODS.getCostDeviationRate());
-        adStatOfHour.setCostHour(adODS.getCost());
-        adStatOfHour.setCompensationAmountHour(adODS.getCompensationAmount());
-        adStatOfHour.setViewCountHour(adODS.getViewCount());
-        adStatOfHour.setThousandDisplayPriceHour(adODS.getThousandDisplayPrice());
-        adStatOfHour.setAvgViewPerUserHour(adODS.getAvgViewPerUser());
-        adStatOfHour.setValidClickCountHour(adODS.getValidClickCount());
-        adStatOfHour.setCtrHour(adODS.getCtr());
-        adStatOfHour.setCpcHour(adODS.getCpc());
-        adStatOfHour.setValuableClickCountHour(adODS.getValuableClickCount());
-        adStatOfHour.setValuableClickRateHour(adODS.getValuableClickRate());
-        adStatOfHour.setValuableClickCostHour(adODS.getValuableClickCost());
-        adStatOfHour.setConversionsCountHour(adODS.getConversionsCount());
-        adStatOfHour.setConversionsCostHour(adODS.getConversionsCost());
-        adStatOfHour.setConversionsRateHour(adODS.getConversionsRate());
-        adStatOfHour.setDeepConversionsCountHour(adODS.getDeepConversionsCount());
-        adStatOfHour.setDeepConversionsCostHour(adODS.getDeepConversionsCost());
-        adStatOfHour.setDeepConversionsRateHour(adODS.getDeepConversionsRate());
-        adStatOfHour.setOrderCountHour(adODS.getOrderCount());
-        adStatOfHour.setFirstDayOrderCountHour(adODS.getFirstDayOrderCount());
-        adStatOfHour.setWebOrderCostHour(adODS.getWebOrderCost());
-        adStatOfHour.setOrderRateHour(adODS.getOrderRate());
-        adStatOfHour.setOrderAmountHour(adODS.getOrderAmount());
-        adStatOfHour.setFirstDayOrderAmountHour(adODS.getFirstDayOrderAmount());
-        adStatOfHour.setOrderUnitPriceHour(adODS.getOrderUnitPrice());
-        adStatOfHour.setOrderRoiHour(adODS.getOrderRoi());
-        adStatOfHour.setSignInCountHour(adODS.getSignInCount());
-        adStatOfHour.setScanFollowCountHour(adODS.getScanFollowCount());
-        adStatOfHour.setWechatAppRegisterUvHour(adODS.getWechatAppRegisterUv());
-        adStatOfHour.setWechatMinigameRegisterCostHour(adODS.getWechatMinigameRegisterCost());
-        adStatOfHour.setWechatMinigameRegisterRateHour(adODS.getWechatMinigameRegisterRate());
-        adStatOfHour.setWechatMinigameArpuHour(adODS.getWechatMinigameArpu());
-        adStatOfHour.setWechatMinigameRetentionCountHour(adODS.getWechatMinigameRetentionCount());
-        adStatOfHour.setWechatMinigameCheckoutCountHour(adODS.getWechatMinigameCheckoutCount());
-        adStatOfHour.setWechatMinigameCheckoutAmountHour(adODS.getWechatMinigameCheckoutAmount());
-        adStatOfHour.setOfficialAccountFollowCountHour(adODS.getOfficialAccountFollowCount());
-        adStatOfHour.setOfficialAccountFollowRateHour(adODS.getOfficialAccountFollowRate());
-        adStatOfHour.setOfficialAccountRegisterUserCountHour(adODS.getOfficialAccountRegisterUserCount());
-        adStatOfHour.setOfficialAccountRegisterRateHour(adODS.getOfficialAccountRegisterRate());
-        adStatOfHour.setOfficialAccountRegisterCostHour(adODS.getOfficialAccountRegisterCost());
-        adStatOfHour.setOfficialAccountRegisterAmountHour(adODS.getOfficialAccountRegisterAmount());
-        adStatOfHour.setOfficialAccountRegisterRoiHour(adODS.getOfficialAccountRegisterRoi());
-        adStatOfHour.setOfficialAccountApplyCountHour(adODS.getOfficialAccountApplyCount());
-        adStatOfHour.setOfficialAccountApplyUserCountHour(adODS.getOfficialAccountApplyUserCount());
-        adStatOfHour.setOfficialAccountApplyRateHour(adODS.getOfficialAccountApplyRate());
-        adStatOfHour.setOfficialAccountApplyCostHour(adODS.getOfficialAccountApplyCost());
-        adStatOfHour.setOfficialAccountApplyAmountHour(adODS.getOfficialAccountApplyAmount());
-        adStatOfHour.setOfficialAccountApplyRoiHour(adODS.getOfficialAccountApplyRoi());
-        adStatOfHour.setOfficialAccountOrderCountHour(adODS.getOfficialAccountOrderCount());
-        adStatOfHour.setOfficialAccountFirstDayOrderCountHour(adODS.getOfficialAccountFirstDayOrderCount());
-        adStatOfHour.setOfficialAccountOrderUserCountHour(adODS.getOfficialAccountOrderUserCount());
-        adStatOfHour.setOfficialAccountOrderRateHour(adODS.getOfficialAccountOrderRate());
-        adStatOfHour.setOfficialAccountOrderCostHour(adODS.getOfficialAccountOrderCost());
-        adStatOfHour.setOfficialAccountOrderAmountHour(adODS.getOfficialAccountOrderAmount());
-        adStatOfHour.setOfficialAccountFirstDayOrderAmountHour(adODS.getOfficialAccountFirstDayOrderAmount());
-        adStatOfHour.setOfficialAccountOrderRoiHour(adODS.getOfficialAccountOrderRoi());
-        adStatOfHour.setOfficialAccountConsultCountHour(adODS.getOfficialAccountConsultCount());
-        adStatOfHour.setOfficialAccountReaderCountHour(adODS.getOfficialAccountReaderCount());
-        adStatOfHour.setOfficialAccountCreditApplyUserCountHour(adODS.getOfficialAccountCreditApplyUserCount());
-        adStatOfHour.setOfficialAccountCreditUserCountHour(adODS.getOfficialAccountCreditUserCount());
-        adStatOfHour.setForwardCountHour(adODS.getForwardCount());
-        adStatOfHour.setForwardUserCountHour(adODS.getForwardUserCount());
-        adStatOfHour.setNoInterestCountHour(adODS.getNoInterestCount());
-        return adStatOfHour;
-    }
-
     public static PlanStatOfHourDWD reduce(PlanStatOfHourDWD value1, AdDataOfHourODS value2, long createTime) {
         PlanStatOfHourDWD result = new PlanStatOfHourDWD();
         BeanUtils.copyProperties(result, value2);
         result.setCreateTime(new Date(createTime));
         if (value1 == null) {
+            Map<Long, Set<Long>> adGroupMap = new HashMap<>();
+            Set<Long> adIdsTemp = new HashSet<>(3);
+            adIdsTemp.add(value2.getAdId());
+            adGroupMap.put(value2.getAdgroupId(), adIdsTemp);
+            result.setAdGroupMap(adGroupMap);
+            result.setAdGroupMapStr(PlanUtil.adGroupMapStr(adGroupMap));
+
+            Set<Long> adIds = new HashSet<>(3);
+            adIds.add(value2.getAdId());
+            result.setAdIds(adIds);
+            result.setAdIdsStr(PlanUtil.adIdsStr(value2.getAdId()));
+
             result.setCompensationAmountTotal(value2.getCompensationAmount());
             result.setCompensationAmountDay(value2.getCompensationAmount());
             result.setCompensationAmountHour(value2.getCompensationAmount());
@@ -1464,6 +1414,11 @@ public class PlanStatOfHourDWD {
             result.setNoInterestCountDay(value2.getNoInterestCount());
             result.setNoInterestCountHour(value2.getNoInterestCount());
         } else {
+            value1.getAdGroupMap().computeIfAbsent(value2.getAdgroupId(), key -> new HashSet<>(3)).add(value2.getAdId());
+            value1.setAdGroupMapStr(PlanUtil.adGroupMapStr(value1.getAdGroupMap()));
+            value1.getAdIds().add(value2.getAdId());
+            value1.setAdIdsStr(PlanUtil.adIdsStr(value1.getAdIds()));
+
             // 是否是同一天
             boolean isUnSameDay = !value1.getStatDay().equals(value2.getStatDay());
             result.setCostDeviationRateTotal(value1.getCostDeviationRateTotal() + value2.getCostDeviationRate());
@@ -1693,8 +1648,12 @@ public class PlanStatOfHourDWD {
         result.setAgencyAccountId(ObjectUtil.toLong(record.get("agency_account_id")));
         result.setWechatAccountId(ObjectUtil.toString(record.get("wechat_account_id")));
         result.setWechatAgencyId(ObjectUtil.toString(record.get("wechat_agency_id")));
-        result.setAdgroupId(ObjectUtil.toLong(record.get("adgroup_id")));
-        result.setAdId(ObjectUtil.toLong(record.get("ad_id")));
+
+        result.setAdGroupMapStr(ObjectUtil.toString(record.get("ad_group_map_str")));
+        result.setAdGroupMap(PlanUtil.parseAdGroupMapStr(result.getAdGroupMapStr()));
+        result.setAdIdsStr(ObjectUtil.toString(record.get("ad_ids_str")));
+        result.setAdIds(PlanUtil.parseAdStr(result.getAdIdsStr()));
+
         result.setCreateTime(ObjectUtil.toDate(record.get("create_time")));
         result.setCostDeviationRateTotal(ObjectUtil.toDouble(record.get("cost_deviation_rate_total")));
         result.setCostDeviationRateDay(ObjectUtil.toDouble(record.get("cost_deviation_rate_day")));

+ 2383 - 0
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/pojo/entity/PlanStatOfMinuteDWD.java

@@ -0,0 +1,2383 @@
+package flink.zanxiangnet.ad.monitoring.pojo.entity;
+
+import com.google.gson.annotations.SerializedName;
+import flink.zanxiangnet.ad.monitoring.maxcompute.bean.annotation.MaxComputeColumn;
+import flink.zanxiangnet.ad.monitoring.maxcompute.bean.annotation.MaxComputeTable;
+import flink.zanxiangnet.ad.monitoring.util.PlanUtil;
+import lombok.Data;
+import org.springframework.beans.BeanUtils;
+
+import java.util.*;
+
+/**
+ * 广告维度的小时统计数据
+ */
+@Data
+@MaxComputeTable("plan_stat_of_minute_dwd")
+public class PlanStatOfMinuteDWD {
+
+    /**
+     * 统计的日期(用于 MaxCompute分区)
+     */
+    @MaxComputeColumn(isPartitioned = true)
+    @SerializedName("stat_day")
+    private String statDay;
+
+    /**
+     * 统计的小时
+     */
+    @SerializedName("hour")
+    private Integer hour;
+
+    /**
+     * 统计发起时间
+     */
+    @SerializedName("stat_time")
+    private Long statTime;
+
+    /**
+     * 腾讯广告应用的账号 id
+     */
+    @SerializedName("account_id")
+    private Long accountId;
+
+    /**
+     * 计划 id
+     */
+    @SerializedName("campaign_id")
+    private Long campaignId;
+
+    /**
+     * 服务商账号 id
+     */
+    @SerializedName("agency_account_id")
+    private Long agencyAccountId;
+
+    /**
+     * 微信账号id
+     */
+    @SerializedName("wechat_account_id")
+    private String wechatAccountId;
+
+    /**
+     * 微信服务商id
+     */
+    @SerializedName("wechat_agency_id")
+    private String wechatAgencyId;
+
+    /**
+     * 广告组与广告的映射关系
+     */
+    @MaxComputeColumn(ignore = true)
+    @SerializedName("ad_group_map")
+    private Map<Long, Set<Long>> adGroupMap;
+
+    /**
+     * 广告组与广告的映射关系
+     */
+    @SerializedName("ad_group_map_str")
+    private String adGroupMapStr;
+
+    /**
+     * 广告 id列表
+     */
+    @MaxComputeColumn(ignore = true)
+    @SerializedName("ad_ids")
+    private Set<Long> adIds;
+
+    /**
+     * 广告 id列表
+     */
+    @SerializedName("ad_ids_str")
+    private String adIdsStr;
+
+    @SerializedName("create_time")
+    private Date createTime;
+
+    /**
+     * 当日成本偏差。反映广告今日的实际成本与目标成本直接的差异,注:该项成本相关数据按小时更新,与实时更新的「转化目标成本」数据存在出入属于正常情况。
+     */
+    @SerializedName("cost_deviation_rate_total")
+    private Double costDeviationRateTotal;
+
+    /**
+     * 当日成本偏差。反映广告今日的实际成本与目标成本直接的差异,注:该项成本相关数据按小时更新,与实时更新的「转化目标成本」数据存在出入属于正常情况。
+     */
+    @SerializedName("cost_deviation_rate_day")
+    private Double costDeviationRateDay;
+
+    /**
+     * 当日成本偏差。反映广告今日的实际成本与目标成本直接的差异,注:该项成本相关数据按小时更新,与实时更新的「转化目标成本」数据存在出入属于正常情况。
+     */
+    @SerializedName("cost_deviation_rate_hour")
+    private Double costDeviationRateHour;
+
+    /**
+     * 当日成本偏差。反映广告今日的实际成本与目标成本直接的差异,注:该项成本相关数据按小时更新,与实时更新的「转化目标成本」数据存在出入属于正常情况。
+     */
+    @SerializedName("cost_deviation_rate_minute")
+    private Double costDeviationRateMinute;
+
+    /**
+     * 消耗
+     */
+    @SerializedName("cost_total")
+    private Long costTotal;
+
+    /**
+     * 消耗
+     */
+    @SerializedName("cost_day")
+    private Long costDay;
+
+    /**
+     * 消耗
+     */
+    @SerializedName("cost_hour")
+    private Long costHour;
+
+    /**
+     * 消耗
+     */
+    @SerializedName("cost_minute")
+    private Long costMinute;
+
+    /**
+     * 赔付金额。智能优化成本保障政策下,广告超成本时的赔付金额。
+     */
+    @SerializedName("compensation_amount_total")
+    private Long compensationAmountTotal;
+
+    /**
+     * 赔付金额。智能优化成本保障政策下,广告超成本时的赔付金额。
+     */
+    @SerializedName("compensation_amount_day")
+    private Long compensationAmountDay;
+
+    /**
+     * 赔付金额。智能优化成本保障政策下,广告超成本时的赔付金额。
+     */
+    @SerializedName("compensation_amount_hour")
+    private Long compensationAmountHour;
+
+    /**
+     * 赔付金额。智能优化成本保障政策下,广告超成本时的赔付金额。
+     */
+    @SerializedName("compensation_amount_minute")
+    private Long compensationAmountMinute;
+
+    /**
+     * 曝光次数。用户观看广告的次数。
+     */
+    @SerializedName("view_count_total")
+    private Long viewCountTotal;
+
+    /**
+     * 曝光次数。用户观看广告的次数。
+     */
+    @SerializedName("view_count_day")
+    private Long viewCountDay;
+
+    /**
+     * 曝光次数。用户观看广告的次数。
+     */
+    @SerializedName("view_count_hour")
+    private Long viewCountHour;
+
+    /**
+     * 曝光次数。用户观看广告的次数。
+     */
+    @SerializedName("view_count_minute")
+    private Long viewCountMinute;
+
+    /**
+     * 千次曝光成本。平均每千次曝光的花费。
+     */
+    @SerializedName("thousand_display_price_all")
+    private Long thousandDisplayPriceAll;
+
+    /**
+     * 千次曝光成本。平均每千次曝光的花费。
+     */
+    @SerializedName("thousand_display_price_day")
+    private Long thousandDisplayPriceDay;
+
+    /**
+     * 千次曝光成本。平均每千次曝光的花费。
+     */
+    @SerializedName("thousand_display_price_hour")
+    private Long thousandDisplayPriceHour;
+
+    /**
+     * 千次曝光成本。平均每千次曝光的花费。
+     */
+    @SerializedName("thousand_display_price_minute")
+    private Long thousandDisplayPriceMinute;
+
+    /**
+     * 人均曝光次数。每个用户观看广告的平均次数。
+     */
+    @SerializedName("avg_view_per_user_all")
+    private Double avgViewPerUserAll;
+
+    /**
+     * 人均曝光次数。每个用户观看广告的平均次数。
+     */
+    @SerializedName("avg_view_per_user_day")
+    private Double avgViewPerUserDay;
+
+    /**
+     * 人均曝光次数。每个用户观看广告的平均次数。
+     */
+    @SerializedName("avg_view_per_user_hour")
+    private Double avgViewPerUserHour;
+
+    /**
+     * 人均曝光次数。每个用户观看广告的平均次数。
+     */
+    @SerializedName("avg_view_per_user_minute")
+    private Double avgViewPerUserMinute;
+
+    /**
+     * 点击次数。用户在广告外层进行点击操作的次数。包括点击图片/视频,及朋友圈广告“文字链、头像、昵称、门店、选择按钮”等所有广告外层区域的点击。
+     */
+    @SerializedName("valid_click_count_total")
+    private Long validClickCountTotal;
+
+    /**
+     * 点击次数。用户在广告外层进行点击操作的次数。包括点击图片/视频,及朋友圈广告“文字链、头像、昵称、门店、选择按钮”等所有广告外层区域的点击。
+     */
+    @SerializedName("valid_click_count_day")
+    private Long validClickCountDay;
+
+    /**
+     * 点击次数。用户在广告外层进行点击操作的次数。包括点击图片/视频,及朋友圈广告“文字链、头像、昵称、门店、选择按钮”等所有广告外层区域的点击。
+     */
+    @SerializedName("valid_click_count_hour")
+    private Long validClickCountHour;
+
+    /**
+     * 点击次数。用户在广告外层进行点击操作的次数。包括点击图片/视频,及朋友圈广告“文字链、头像、昵称、门店、选择按钮”等所有广告外层区域的点击。
+     */
+    @SerializedName("valid_click_count_minute")
+    private Long validClickCountMinute;
+
+    /**
+     * 点击率。看到广告后执行点击操作的百分比。计算逻辑:广告点击次数/广告曝光次数。
+     */
+    @SerializedName("ctr_all")
+    private Double ctrAll;
+
+    /**
+     * 点击率。看到广告后执行点击操作的百分比。计算逻辑:广告点击次数/广告曝光次数。
+     */
+    @SerializedName("ctr_day")
+    private Double ctrDay;
+
+    /**
+     * 点击率。看到广告后执行点击操作的百分比。计算逻辑:广告点击次数/广告曝光次数。
+     */
+    @SerializedName("ctr_hour")
+    private Double ctrHour;
+
+    /**
+     * 点击率。看到广告后执行点击操作的百分比。计算逻辑:广告点击次数/广告曝光次数。
+     */
+    @SerializedName("ctr_minute")
+    private Double ctrMinute;
+
+    /**
+     * 点击均价。一次广告点击的平均花费。计算逻辑:广告花费/广告点击次数。
+     */
+    @SerializedName("cpc_all")
+    private Long cpcAll;
+
+    /**
+     * 点击均价。一次广告点击的平均花费。计算逻辑:广告花费/广告点击次数。
+     */
+    @SerializedName("cpc_day")
+    private Long cpcDay;
+
+    /**
+     * 点击均价。一次广告点击的平均花费。计算逻辑:广告花费/广告点击次数。
+     */
+    @SerializedName("cpc_hour")
+    private Long cpcHour;
+
+    /**
+     * 点击均价。一次广告点击的平均花费。计算逻辑:广告花费/广告点击次数。
+     */
+    @SerializedName("cpc_minute")
+    private Long cpcMinute;
+
+    /**
+     * 可转化点击次数。朋友圈:可转化点击是指可能产生转化的外层点击次数。对于“公众号推广”的广告,包括外层的公众号头像、公众号昵称、详情页查看、原生推广页查看; 对于其他类型的广告,包括外层的详情页查看和原生推广页查看。公众号:可转化点击是指可能产生转化的点击次数。
+     */
+    @SerializedName("valuable_click_count_total")
+    private Long valuableClickCountTotal;
+
+    /**
+     * 可转化点击次数。朋友圈:可转化点击是指可能产生转化的外层点击次数。对于“公众号推广”的广告,包括外层的公众号头像、公众号昵称、详情页查看、原生推广页查看; 对于其他类型的广告,包括外层的详情页查看和原生推广页查看。公众号:可转化点击是指可能产生转化的点击次数。
+     */
+    @SerializedName("valuable_click_count_day")
+    private Long valuableClickCountDay;
+
+    /**
+     * 可转化点击次数。朋友圈:可转化点击是指可能产生转化的外层点击次数。对于“公众号推广”的广告,包括外层的公众号头像、公众号昵称、详情页查看、原生推广页查看; 对于其他类型的广告,包括外层的详情页查看和原生推广页查看。公众号:可转化点击是指可能产生转化的点击次数。
+     */
+    @SerializedName("valuable_click_count_hour")
+    private Long valuableClickCountHour;
+
+    /**
+     * 可转化点击次数。朋友圈:可转化点击是指可能产生转化的外层点击次数。对于“公众号推广”的广告,包括外层的公众号头像、公众号昵称、详情页查看、原生推广页查看; 对于其他类型的广告,包括外层的详情页查看和原生推广页查看。公众号:可转化点击是指可能产生转化的点击次数。
+     */
+    @SerializedName("valuable_click_count_minute")
+    private Long valuableClickCountMinute;
+
+    /**
+     * 可转化点击率。用户看到广告后执行可转化点击操作的百分比。计算逻辑:广告可转化点击次数/广告曝光次数。
+     */
+    @SerializedName("valuable_click_rate_all")
+    private Double valuableClickRateAll;
+
+    /**
+     * 可转化点击率。用户看到广告后执行可转化点击操作的百分比。计算逻辑:广告可转化点击次数/广告曝光次数。
+     */
+    @SerializedName("valuable_click_rate_day")
+    private Double valuableClickRateDay;
+
+    /**
+     * 可转化点击率。用户看到广告后执行可转化点击操作的百分比。计算逻辑:广告可转化点击次数/广告曝光次数。
+     */
+    @SerializedName("valuable_click_rate_hour")
+    private Double valuableClickRateHour;
+
+    /**
+     * 可转化点击率。用户看到广告后执行可转化点击操作的百分比。计算逻辑:广告可转化点击次数/广告曝光次数。
+     */
+    @SerializedName("valuable_click_rate_minute")
+    private Double valuableClickRateMinute;
+
+    /**
+     * 可转化点击成本。一次可转化点击的平均花费。计算逻辑:广告花费/可转化点击次数。
+     */
+    @SerializedName("valuable_click_cost_all")
+    private Long valuableClickCostAll;
+
+    /**
+     * 可转化点击成本。一次可转化点击的平均花费。计算逻辑:广告花费/可转化点击次数。
+     */
+    @SerializedName("valuable_click_cost_day")
+    private Long valuableClickCostDay;
+
+    /**
+     * 可转化点击成本。一次可转化点击的平均花费。计算逻辑:广告花费/可转化点击次数。
+     */
+    @SerializedName("valuable_click_cost_hour")
+    private Long valuableClickCostHour;
+
+    /**
+     * 可转化点击成本。一次可转化点击的平均花费。计算逻辑:广告花费/可转化点击次数。
+     */
+    @SerializedName("valuable_click_cost_minute")
+    private Long valuableClickCostMinute;
+
+    /**
+     * 转化目标量。「转化目标」的具体数量,代表该广告的转化效果量级。
+     */
+    @SerializedName("conversions_count_total")
+    private Long conversionsCountTotal;
+
+    /**
+     * 转化目标量。「转化目标」的具体数量,代表该广告的转化效果量级。
+     */
+    @SerializedName("conversions_count_day")
+    private Long conversionsCountDay;
+
+    /**
+     * 转化目标量。「转化目标」的具体数量,代表该广告的转化效果量级。
+     */
+    @SerializedName("conversions_count_hour")
+    private Long conversionsCountHour;
+
+    /**
+     * 转化目标量。「转化目标」的具体数量,代表该广告的转化效果量级。
+     */
+    @SerializedName("conversions_count_minute")
+    private Long conversionsCountMinute;
+
+    /**
+     * 转化目标成本。广告产生一次转化目标的平均费用。计算逻辑:广告花费/转化目标量。
+     */
+    @SerializedName("conversions_cost_all")
+    private Long conversionsCostAll;
+
+    /**
+     * 转化目标成本。广告产生一次转化目标的平均费用。计算逻辑:广告花费/转化目标量。
+     */
+    @SerializedName("conversions_cost_day")
+    private Long conversionsCostDay;
+
+    /**
+     * 转化目标成本。广告产生一次转化目标的平均费用。计算逻辑:广告花费/转化目标量。
+     */
+    @SerializedName("conversions_cost_hour")
+    private Long conversionsCostHour;
+
+    /**
+     * 转化目标成本。广告产生一次转化目标的平均费用。计算逻辑:广告花费/转化目标量。
+     */
+    @SerializedName("conversions_cost_minute")
+    private Long conversionsCostMinute;
+
+    /**
+     * 目标转化率。朋友圈:转化目标量/可转化点击次数。公众号:转化目标量/点击次数。
+     */
+    @SerializedName("conversions_rate_all")
+    private Double conversionsRateAll;
+
+    /**
+     * 目标转化率。朋友圈:转化目标量/可转化点击次数。公众号:转化目标量/点击次数。
+     */
+    @SerializedName("conversions_rate_day")
+    private Double conversionsRateDay;
+
+    /**
+     * 目标转化率。朋友圈:转化目标量/可转化点击次数。公众号:转化目标量/点击次数。
+     */
+    @SerializedName("conversions_rate_hour")
+    private Double conversionsRateHour;
+
+    /**
+     * 目标转化率。朋友圈:转化目标量/可转化点击次数。公众号:转化目标量/点击次数。
+     */
+    @SerializedName("conversions_rate_minute")
+    private Double conversionsRateMinute;
+
+    /**
+     * 深度转化目标量-灰度中。根据您选择的深度智能优化目标,该广告对应的具体数量。部分需接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_count_total")
+    private Long deepConversionsCountTotal;
+
+    /**
+     * 深度转化目标量-灰度中。根据您选择的深度智能优化目标,该广告对应的具体数量。部分需接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_count_day")
+    private Long deepConversionsCountDay;
+
+    /**
+     * 深度转化目标量-灰度中。根据您选择的深度智能优化目标,该广告对应的具体数量。部分需接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_count_hour")
+    private Long deepConversionsCountHour;
+
+    /**
+     * 深度转化目标量-灰度中。根据您选择的深度智能优化目标,该广告对应的具体数量。部分需接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_count_minute")
+    private Long deepConversionsCountMinute;
+
+    /**
+     * 深度转化目标成本-灰度中。根据您选择的深度智能优化目标,该广告产生一次转化的平均费用。计算逻辑:广告花费/深度转化目标量。部分需接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_cost_all")
+    private Long deepConversionsCostAll;
+
+    /**
+     * 深度转化目标成本-灰度中。根据您选择的深度智能优化目标,该广告产生一次转化的平均费用。计算逻辑:广告花费/深度转化目标量。部分需接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_cost_day")
+    private Long deepConversionsCostDay;
+
+    /**
+     * 深度转化目标成本-灰度中。根据您选择的深度智能优化目标,该广告产生一次转化的平均费用。计算逻辑:广告花费/深度转化目标量。部分需接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_cost_hour")
+    private Long deepConversionsCostHour;
+
+    /**
+     * 深度转化目标成本-灰度中。根据您选择的深度智能优化目标,该广告产生一次转化的平均费用。计算逻辑:广告花费/深度转化目标量。部分需接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_cost_minute")
+    private Long deepConversionsCostMinute;
+
+    /**
+     * 深度目标转化率-灰度中。朋友圈:深度转化目标量/可转化点击次数。公众号:深度转化目标量/点击次数。指标随深度转化功能灰度中。接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_rate_all")
+    private Double deepConversionsRateAll;
+
+    /**
+     * 深度目标转化率-灰度中。朋友圈:深度转化目标量/可转化点击次数。公众号:深度转化目标量/点击次数。指标随深度转化功能灰度中。接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_rate_day")
+    private Double deepConversionsRateDay;
+
+    /**
+     * 深度目标转化率-灰度中。朋友圈:深度转化目标量/可转化点击次数。公众号:深度转化目标量/点击次数。指标随深度转化功能灰度中。接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_rate_hour")
+    private Double deepConversionsRateHour;
+
+    /**
+     * 深度目标转化率-灰度中。朋友圈:深度转化目标量/可转化点击次数。公众号:深度转化目标量/点击次数。指标随深度转化功能灰度中。接入转化跟踪后可统计。
+     */
+    @SerializedName("deep_conversions_rate_minute")
+    private Double deepConversionsRateMinute;
+
+    /**
+     * 下单量。用户通过该广告进行商品成交(如下单提交、在线支付)的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_count_total")
+    private Long orderCountTotal;
+
+    /**
+     * 下单量。用户通过该广告进行商品成交(如下单提交、在线支付)的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_count_day")
+    private Long orderCountDay;
+
+    /**
+     * 下单量。用户通过该广告进行商品成交(如下单提交、在线支付)的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_count_hour")
+    private Long orderCountHour;
+
+    /**
+     * 下单量。用户通过该广告进行商品成交(如下单提交、在线支付)的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_count_minute")
+    private Long orderCountMinute;
+
+    /**
+     * 首日新增下单量。广告推广获取的用户,点击广告当日,带来的下单次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("first_day_order_count_total")
+    private Long firstDayOrderCountTotal;
+
+    /**
+     * 首日新增下单量。广告推广获取的用户,点击广告当日,带来的下单次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("first_day_order_count_day")
+    private Long firstDayOrderCountDay;
+
+    /**
+     * 首日新增下单量。广告推广获取的用户,点击广告当日,带来的下单次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("first_day_order_count_hour")
+    private Long firstDayOrderCountHour;
+
+    /**
+     * 首日新增下单量。广告推广获取的用户,点击广告当日,带来的下单次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("first_day_order_count_minute")
+    private Long firstDayOrderCountMinute;
+
+    /**
+     * 下单成本(次数)。产生一次下单的成本。
+     */
+    @SerializedName("web_order_cost_all")
+    private Long webOrderCostAll;
+
+    /**
+     * 下单成本(次数)。产生一次下单的成本。
+     */
+    @SerializedName("web_order_cost_day")
+    private Long webOrderCostDay;
+
+    /**
+     * 下单成本(次数)。产生一次下单的成本。
+     */
+    @SerializedName("web_order_cost_hour")
+    private Long webOrderCostHour;
+
+    /**
+     * 下单成本(次数)。产生一次下单的成本。
+     */
+    @SerializedName("web_order_cost_minute")
+    private Long webOrderCostMinute;
+
+    /**
+     * 下单率。一次点击到下单的转化率。
+     */
+    @SerializedName("order_rate_all")
+    private Double orderRateAll;
+
+    /**
+     * 下单率。一次点击到下单的转化率。
+     */
+    @SerializedName("order_rate_day")
+    private Double orderRateDay;
+
+    /**
+     * 下单率。一次点击到下单的转化率。
+     */
+    @SerializedName("order_rate_hour")
+    private Double orderRateHour;
+
+    /**
+     * 下单率。一次点击到下单的转化率。
+     */
+    @SerializedName("order_rate_minute")
+    private Double orderRateMinute;
+
+    /**
+     * 下单金额。广告带来的总订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_amount_total")
+    private Long orderAmountTotal;
+
+    /**
+     * 下单金额。广告带来的总订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_amount_day")
+    private Long orderAmountDay;
+
+    /**
+     * 下单金额。广告带来的总订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_amount_hour")
+    private Long orderAmountHour;
+
+    /**
+     * 下单金额。广告带来的总订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_amount_minute")
+    private Long orderAmountMinute;
+
+    /**
+     * 首日新增下单金额。广告推广获取的用户,点击广告当日,带来的总订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("first_day_order_amount_total")
+    private Long firstDayOrderAmountTotal;
+
+    /**
+     * 首日新增下单金额。广告推广获取的用户,点击广告当日,带来的总订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("first_day_order_amount_day")
+    private Long firstDayOrderAmountDay;
+
+    /**
+     * 首日新增下单金额。广告推广获取的用户,点击广告当日,带来的总订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("first_day_order_amount_hour")
+    private Long firstDayOrderAmountHour;
+
+    /**
+     * 首日新增下单金额。广告推广获取的用户,点击广告当日,带来的总订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("first_day_order_amount_minute")
+    private Long firstDayOrderAmountMinute;
+
+    /**
+     * 下单客单价。下单金额/下单量。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_unit_price_all")
+    private Long orderUnitPriceAll;
+
+    /**
+     * 下单客单价。下单金额/下单量。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_unit_price_day")
+    private Long orderUnitPriceDay;
+
+    /**
+     * 下单客单价。下单金额/下单量。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_unit_price_hour")
+    private Long orderUnitPriceHour;
+
+    /**
+     * 下单客单价。下单金额/下单量。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_unit_price_minute")
+    private Long orderUnitPriceMinute;
+
+    /**
+     * 下单ROI。下单金额/广告花费。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_roi_all")
+    private Double orderRoiAll;
+
+    /**
+     * 下单ROI。下单金额/广告花费。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_roi_day")
+    private Double orderRoiDay;
+
+    /**
+     * 下单ROI。下单金额/广告花费。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_roi_hour")
+    private Double orderRoiHour;
+
+    /**
+     * 下单ROI。下单金额/广告花费。接入转化跟踪后可统计。
+     */
+    @SerializedName("order_roi_minute")
+    private Double orderRoiMinute;
+
+    /**
+     * 签收次数。签收从广告主处购买的商品的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("sign_in_count_total")
+    private Long signInCountTotal;
+
+    /**
+     * 签收次数。签收从广告主处购买的商品的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("sign_in_count_day")
+    private Long signInCountDay;
+
+    /**
+     * 签收次数。签收从广告主处购买的商品的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("sign_in_count_hour")
+    private Long signInCountHour;
+
+    /**
+     * 签收次数。签收从广告主处购买的商品的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("sign_in_count_minute")
+    private Long signInCountMinute;
+
+    /**
+     * 加企业微信客服人数。添加企业微信好友成功的独立用户数。
+     */
+    @SerializedName("scan_follow_count_total")
+    private Long scanFollowCountTotal;
+
+    /**
+     * 加企业微信客服人数。添加企业微信好友成功的独立用户数。
+     */
+    @SerializedName("scan_follow_count_day")
+    private Long scanFollowCountDay;
+
+    /**
+     * 加企业微信客服人数。添加企业微信好友成功的独立用户数。
+     */
+    @SerializedName("scan_follow_count_hour")
+    private Long scanFollowCountHour;
+
+    /**
+     * 加企业微信客服人数。添加企业微信好友成功的独立用户数。
+     */
+    @SerializedName("scan_follow_count_minute")
+    private Long scanFollowCountMinute;
+
+    /**
+     * 小游戏注册人数。通过广告首次登录小游戏的独立用户数。
+     */
+    @SerializedName("wechat_app_register_uv_total")
+    private Long wechatAppRegisterUvTotal;
+
+    /**
+     * 小游戏注册人数。通过广告首次登录小游戏的独立用户数。
+     */
+    @SerializedName("wechat_app_register_uv_day")
+    private Long wechatAppRegisterUvDay;
+
+    /**
+     * 小游戏注册人数。通过广告首次登录小游戏的独立用户数。
+     */
+    @SerializedName("wechat_app_register_uv_hour")
+    private Long wechatAppRegisterUvHour;
+
+    /**
+     * 小游戏注册人数。通过广告首次登录小游戏的独立用户数。
+     */
+    @SerializedName("wechat_app_register_uv_minute")
+    private Long wechatAppRegisterUvMinute;
+
+    /**
+     * 小游戏注册成本(人数)。产生一个小游戏注册人数的成本。
+     */
+    @SerializedName("wechat_minigame_register_cost_all")
+    private Long wechatMinigameRegisterCostAll;
+
+    /**
+     * 小游戏注册成本(人数)。产生一个小游戏注册人数的成本。
+     */
+    @SerializedName("wechat_minigame_register_cost_day")
+    private Long wechatMinigameRegisterCostDay;
+
+    /**
+     * 小游戏注册成本(人数)。产生一个小游戏注册人数的成本。
+     */
+    @SerializedName("wechat_minigame_register_cost_hour")
+    private Long wechatMinigameRegisterCostHour;
+
+    /**
+     * 小游戏注册成本(人数)。产生一个小游戏注册人数的成本。
+     */
+    @SerializedName("wechat_minigame_register_cost_minute")
+    private Long wechatMinigameRegisterCostMinute;
+
+    /**
+     * 小游戏注册率。一次点击到小游戏注册的转化率。
+     */
+    @SerializedName("wechat_minigame_register_rate_all")
+    private Double wechatMinigameRegisterRateAll;
+
+    /**
+     * 小游戏注册率。一次点击到小游戏注册的转化率。
+     */
+    @SerializedName("wechat_minigame_register_rate_day")
+    private Double wechatMinigameRegisterRateDay;
+
+    /**
+     * 小游戏注册率。一次点击到小游戏注册的转化率。
+     */
+    @SerializedName("wechat_minigame_register_rate_hour")
+    private Double wechatMinigameRegisterRateHour;
+
+    /**
+     * 小游戏注册率。一次点击到小游戏注册的转化率。
+     */
+    @SerializedName("wechat_minigame_register_rate_minute")
+    private Double wechatMinigameRegisterRateMinute;
+
+    /**
+     * 首日新增广告ARPU。广告带来的注册用户,在注册当日,产生的平均广告变现收入。注:该指标天更新,可以查看昨天及以前的数据。
+     */
+    @SerializedName("wechat_minigame_arpu_all")
+    private Double wechatMinigameArpuAll;
+
+    /**
+     * 首日新增广告ARPU。广告带来的注册用户,在注册当日,产生的平均广告变现收入。注:该指标天更新,可以查看昨天及以前的数据。
+     */
+    @SerializedName("wechat_minigame_arpu_day")
+    private Double wechatMinigameArpuDay;
+
+    /**
+     * 首日新增广告ARPU。广告带来的注册用户,在注册当日,产生的平均广告变现收入。注:该指标天更新,可以查看昨天及以前的数据。
+     */
+    @SerializedName("wechat_minigame_arpu_hour")
+    private Double wechatMinigameArpuHour;
+
+    /**
+     * 首日新增广告ARPU。广告带来的注册用户,在注册当日,产生的平均广告变现收入。注:该指标天更新,可以查看昨天及以前的数据。
+     */
+    @SerializedName("wechat_minigame_arpu_minute")
+    private Double wechatMinigameArpuMinute;
+
+    /**
+     * 小游戏次留人数。通过广告首次登录小游戏,并在第二天再次登录的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_retention_count_total")
+    private Long wechatMinigameRetentionCountTotal;
+
+    /**
+     * 小游戏次留人数。通过广告首次登录小游戏,并在第二天再次登录的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_retention_count_day")
+    private Long wechatMinigameRetentionCountDay;
+
+    /**
+     * 小游戏次留人数。通过广告首次登录小游戏,并在第二天再次登录的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_retention_count_hour")
+    private Long wechatMinigameRetentionCountHour;
+
+    /**
+     * 小游戏次留人数。通过广告首次登录小游戏,并在第二天再次登录的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_retention_count_minute")
+    private Long wechatMinigameRetentionCountMinute;
+
+    /**
+     * 小游戏付费次数。通过广告进入小游戏并完成付费的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_checkout_count_total")
+    private Long wechatMinigameCheckoutCountTotal;
+
+    /**
+     * 小游戏付费次数。通过广告进入小游戏并完成付费的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_checkout_count_day")
+    private Long wechatMinigameCheckoutCountDay;
+
+    /**
+     * 小游戏付费次数。通过广告进入小游戏并完成付费的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_checkout_count_hour")
+    private Long wechatMinigameCheckoutCountHour;
+
+    /**
+     * 小游戏付费次数。通过广告进入小游戏并完成付费的次数。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_checkout_count_minute")
+    private Long wechatMinigameCheckoutCountMinute;
+
+    /**
+     * 小游戏付费金额。通过广告进入小游戏并完成付费的金额。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_checkout_amount_total")
+    private Long wechatMinigameCheckoutAmountTotal;
+
+    /**
+     * 小游戏付费金额。通过广告进入小游戏并完成付费的金额。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_checkout_amount_day")
+    private Long wechatMinigameCheckoutAmountDay;
+
+    /**
+     * 小游戏付费金额。通过广告进入小游戏并完成付费的金额。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_checkout_amount_hour")
+    private Long wechatMinigameCheckoutAmountHour;
+
+    /**
+     * 小游戏付费金额。通过广告进入小游戏并完成付费的金额。接入转化跟踪后可统计。
+     */
+    @SerializedName("wechat_minigame_checkout_amount_minute")
+    private Long wechatMinigameCheckoutAmountMinute;
+
+    /**
+     * 公众号关注次数。用户通过广告关注公众号成功的次数。
+     */
+    @SerializedName("official_account_follow_count_total")
+    private Long officialAccountFollowCountTotal;
+
+    /**
+     * 公众号关注次数。用户通过广告关注公众号成功的次数。
+     */
+    @SerializedName("official_account_follow_count_day")
+    private Long officialAccountFollowCountDay;
+
+    /**
+     * 公众号关注次数。用户通过广告关注公众号成功的次数。
+     */
+    @SerializedName("official_account_follow_count_hour")
+    private Long officialAccountFollowCountHour;
+
+    /**
+     * 公众号关注次数。用户通过广告关注公众号成功的次数。
+     */
+    @SerializedName("official_account_follow_count_minute")
+    private Long officialAccountFollowCountMinute;
+
+    /**
+     * 公众号关注率。一次点击到公众号关注的转化率。
+     */
+    @SerializedName("official_account_follow_rate_all")
+    private Double officialAccountFollowRateAll;
+
+    /**
+     * 公众号关注率。一次点击到公众号关注的转化率。
+     */
+    @SerializedName("official_account_follow_rate_day")
+    private Double officialAccountFollowRateDay;
+
+    /**
+     * 公众号关注率。一次点击到公众号关注的转化率。
+     */
+    @SerializedName("official_account_follow_rate_hour")
+    private Double officialAccountFollowRateHour;
+
+    /**
+     * 公众号关注率。一次点击到公众号关注的转化率。
+     */
+    @SerializedName("official_account_follow_rate_minute")
+    private Double officialAccountFollowRateMinute;
+
+    /**
+     * 公众号内注册人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的注册行为的人数(UV)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_user_count_total")
+    private Long officialAccountRegisterUserCountTotal;
+
+    /**
+     * 公众号内注册人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的注册行为的人数(UV)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_user_count_day")
+    private Long officialAccountRegisterUserCountDay;
+
+    /**
+     * 公众号内注册人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的注册行为的人数(UV)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_user_count_hour")
+    private Long officialAccountRegisterUserCountHour;
+
+    /**
+     * 公众号内注册人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的注册行为的人数(UV)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_user_count_minute")
+    private Long officialAccountRegisterUserCountMinute;
+
+    /**
+     * 公众号内注册比例。公众号内注册独立用户数/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_rate_all")
+    private Double officialAccountRegisterRateAll;
+
+    /**
+     * 公众号内注册比例。公众号内注册独立用户数/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_rate_day")
+    private Double officialAccountRegisterRateDay;
+
+    /**
+     * 公众号内注册比例。公众号内注册独立用户数/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_rate_hour")
+    private Double officialAccountRegisterRateHour;
+
+    /**
+     * 公众号内注册比例。公众号内注册独立用户数/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_rate_minute")
+    private Double officialAccountRegisterRateMinute;
+
+    /**
+     * 公众号内注册成本。广告花费/广告产生的注册行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_cost_all")
+    private Long officialAccountRegisterCostAll;
+
+    /**
+     * 公众号内注册成本。广告花费/广告产生的注册行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_cost_day")
+    private Long officialAccountRegisterCostDay;
+
+    /**
+     * 公众号内注册成本。广告花费/广告产生的注册行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_cost_hour")
+    private Long officialAccountRegisterCostHour;
+
+    /**
+     * 公众号内注册成本。广告花费/广告产生的注册行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_cost_minute")
+    private Long officialAccountRegisterCostMinute;
+
+    /**
+     * 公众号内注册订单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的订单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_amount_total")
+    private Long officialAccountRegisterAmountTotal;
+
+    /**
+     * 公众号内注册订单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的订单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_amount_day")
+    private Long officialAccountRegisterAmountDay;
+
+    /**
+     * 公众号内注册订单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的订单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_amount_hour")
+    private Long officialAccountRegisterAmountHour;
+
+    /**
+     * 公众号内注册订单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的订单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_amount_minute")
+    private Long officialAccountRegisterAmountMinute;
+
+    /**
+     * 公众号内注册ROI。注册产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_roi_all")
+    private Long officialAccountRegisterRoiAll;
+
+    /**
+     * 公众号内注册ROI。注册产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_roi_day")
+    private Long officialAccountRegisterRoiDay;
+
+    /**
+     * 公众号内注册ROI。注册产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_roi_hour")
+    private Long officialAccountRegisterRoiHour;
+
+    /**
+     * 公众号内注册ROI。注册产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_register_roi_minute")
+    private Long officialAccountRegisterRoiMinute;
+
+    /**
+     * 公众号内填单次数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的数量。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_count_total")
+    private Long officialAccountApplyCountTotal;
+
+    /**
+     * 公众号内填单次数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的数量。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_count_day")
+    private Long officialAccountApplyCountDay;
+
+    /**
+     * 公众号内填单次数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的数量。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_count_hour")
+    private Long officialAccountApplyCountHour;
+
+    /**
+     * 公众号内填单次数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的数量。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_count_minute")
+    private Long officialAccountApplyCountMinute;
+
+    /**
+     * 公众号内填单人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的独立用户数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_user_count_total")
+    private Long officialAccountApplyUserCountTotal;
+
+    /**
+     * 公众号内填单人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的独立用户数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_user_count_day")
+    private Long officialAccountApplyUserCountDay;
+
+    /**
+     * 公众号内填单人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的独立用户数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_user_count_hour")
+    private Long officialAccountApplyUserCountHour;
+
+    /**
+     * 公众号内填单人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的独立用户数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_user_count_minute")
+    private Long officialAccountApplyUserCountMinute;
+
+    /**
+     * 公众号内填单比例。公众号内填单的独立用户数/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_rate_all")
+    private Double officialAccountApplyRateAll;
+
+    /**
+     * 公众号内填单比例。公众号内填单的独立用户数/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_rate_day")
+    private Double officialAccountApplyRateDay;
+
+    /**
+     * 公众号内填单比例。公众号内填单的独立用户数/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_rate_hour")
+    private Double officialAccountApplyRateHour;
+
+    /**
+     * 公众号内填单比例。公众号内填单的独立用户数/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_rate_minute")
+    private Double officialAccountApplyRateMinute;
+
+    /**
+     * 公众号内填单成本。广告花费/广告产生的填单行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_apply_cost_all")
+    private Long officialAccountApplyCostAll;
+
+    /**
+     * 公众号内填单成本。广告花费/广告产生的填单行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_apply_cost_day")
+    private Long officialAccountApplyCostDay;
+
+    /**
+     * 公众号内填单成本。广告花费/广告产生的填单行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_apply_cost_hour")
+    private Long officialAccountApplyCostHour;
+
+    /**
+     * 公众号内填单成本。广告花费/广告产生的填单行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_apply_cost_minute")
+    private Long officialAccountApplyCostMinute;
+
+    /**
+     * 公众号内填单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_amount_total")
+    private Long officialAccountApplyAmountTotal;
+
+    /**
+     * 公众号内填单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_amount_day")
+    private Long officialAccountApplyAmountDay;
+
+    /**
+     * 公众号内填单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_amount_hour")
+    private Long officialAccountApplyAmountHour;
+
+    /**
+     * 公众号内填单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的填单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放)。
+     */
+    @SerializedName("official_account_apply_amount_minute")
+    private Long officialAccountApplyAmountMinute;
+
+    /**
+     * 公众号内填单ROI。填单产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_apply_roi_all")
+    private Long officialAccountApplyRoiAll;
+
+    /**
+     * 公众号内填单ROI。填单产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_apply_roi_day")
+    private Long officialAccountApplyRoiDay;
+
+    /**
+     * 公众号内填单ROI。填单产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_apply_roi_hour")
+    private Long officialAccountApplyRoiHour;
+
+    /**
+     * 公众号内填单ROI。填单产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_apply_roi_minute")
+    private Long officialAccountApplyRoiMinute;
+
+    /**
+     * 公众号内下单次数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_count_total")
+    private Long officialAccountOrderCountTotal;
+
+    /**
+     * 公众号内下单次数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_count_day")
+    private Long officialAccountOrderCountDay;
+
+    /**
+     * 公众号内下单次数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_count_hour")
+    private Long officialAccountOrderCountHour;
+
+    /**
+     * 公众号内下单次数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_count_minute")
+    private Long officialAccountOrderCountMinute;
+
+    /**
+     * 首日公众号内下单次数。广告推广获取的用户,在关注公众号当日,在公众号内部产生了广告主定义的下单行为数量。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_first_day_order_count_total")
+    private Long officialAccountFirstDayOrderCountTotal;
+
+    /**
+     * 首日公众号内下单次数。广告推广获取的用户,在关注公众号当日,在公众号内部产生了广告主定义的下单行为数量。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_first_day_order_count_day")
+    private Long officialAccountFirstDayOrderCountDay;
+
+    /**
+     * 首日公众号内下单次数。广告推广获取的用户,在关注公众号当日,在公众号内部产生了广告主定义的下单行为数量。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_first_day_order_count_hour")
+    private Long officialAccountFirstDayOrderCountHour;
+
+    /**
+     * 首日公众号内下单次数。广告推广获取的用户,在关注公众号当日,在公众号内部产生了广告主定义的下单行为数量。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_first_day_order_count_minute")
+    private Long officialAccountFirstDayOrderCountMinute;
+
+    /**
+     * 公众号内下单人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的独立用户数。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_user_count_total")
+    private Long officialAccountOrderUserCountTotal;
+
+    /**
+     * 公众号内下单人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的独立用户数。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_user_count_day")
+    private Long officialAccountOrderUserCountDay;
+
+    /**
+     * 公众号内下单人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的独立用户数。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_user_count_hour")
+    private Long officialAccountOrderUserCountHour;
+
+    /**
+     * 公众号内下单人数。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的独立用户数。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_user_count_minute")
+    private Long officialAccountOrderUserCountMinute;
+
+    /**
+     * 公众号内下单比例。公众号内下单独立用户数(UV)/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_rate_all")
+    private Double officialAccountOrderRateAll;
+
+    /**
+     * 公众号内下单比例。公众号内下单独立用户数(UV)/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_rate_day")
+    private Double officialAccountOrderRateDay;
+
+    /**
+     * 公众号内下单比例。公众号内下单独立用户数(UV)/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_rate_hour")
+    private Double officialAccountOrderRateHour;
+
+    /**
+     * 公众号内下单比例。公众号内下单独立用户数(UV)/公众号关注次数。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_rate_minute")
+    private Double officialAccountOrderRateMinute;
+
+    /**
+     * 公众号内下单成本。广告花费/广告产生的下单行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_cost_all")
+    private Long officialAccountOrderCostAll;
+
+    /**
+     * 公众号内下单成本。广告花费/广告产生的下单行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_cost_day")
+    private Long officialAccountOrderCostDay;
+
+    /**
+     * 公众号内下单成本。广告花费/广告产生的下单行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_cost_hour")
+    private Long officialAccountOrderCostHour;
+
+    /**
+     * 公众号内下单成本。广告花费/广告产生的下单行为数量。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_cost_minute")
+    private Long officialAccountOrderCostMinute;
+
+    /**
+     * 公众号内下单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_amount_total")
+    private Long officialAccountOrderAmountTotal;
+
+    /**
+     * 公众号内下单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_amount_day")
+    private Long officialAccountOrderAmountDay;
+
+    /**
+     * 公众号内下单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_amount_hour")
+    private Long officialAccountOrderAmountHour;
+
+    /**
+     * 公众号内下单金额。用户通过关注类广告关注公众号后,在公众号内部产生了广告主定义的下单行为的订单金额(即销售额)。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_amount_minute")
+    private Long officialAccountOrderAmountMinute;
+
+    /**
+     * 首日公众号内下单金额。广告推广获取的用户,在关注公众号当日,在公众号内部产生了广告主定义的下单行为订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_first_day_order_amount_total")
+    private Long officialAccountFirstDayOrderAmountTotal;
+
+    /**
+     * 首日公众号内下单金额。广告推广获取的用户,在关注公众号当日,在公众号内部产生了广告主定义的下单行为订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_first_day_order_amount_day")
+    private Long officialAccountFirstDayOrderAmountDay;
+
+    /**
+     * 首日公众号内下单金额。广告推广获取的用户,在关注公众号当日,在公众号内部产生了广告主定义的下单行为订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_first_day_order_amount_hour")
+    private Long officialAccountFirstDayOrderAmountHour;
+
+    /**
+     * 首日公众号内下单金额。广告推广获取的用户,在关注公众号当日,在公众号内部产生了广告主定义的下单行为订单金额(即销售额)。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_first_day_order_amount_minute")
+    private Long officialAccountFirstDayOrderAmountMinute;
+
+    /**
+     * 公众号内下单ROI。下单产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_roi_all")
+    private Long officialAccountOrderRoiAll;
+
+    /**
+     * 公众号内下单ROI。下单产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_roi_day")
+    private Long officialAccountOrderRoiDay;
+
+    /**
+     * 公众号内下单ROI。下单产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_roi_hour")
+    private Long officialAccountOrderRoiHour;
+
+    /**
+     * 公众号内下单ROI。下单产生的订单金额累计/广告花费。接入转化跟踪后可统计(公众号接入暂未全量开放) 。
+     */
+    @SerializedName("official_account_order_roi_minute")
+    private Long officialAccountOrderRoiMinute;
+
+    /**
+     * 公众号内发消息人数。用户关注公众号后,在公众号对话框内发送消息的独立用户数。
+     */
+    @SerializedName("official_account_consult_count_total")
+    private Long officialAccountConsultCountTotal;
+
+    /**
+     * 公众号内发消息人数。用户关注公众号后,在公众号对话框内发送消息的独立用户数。
+     */
+    @SerializedName("official_account_consult_count_day")
+    private Long officialAccountConsultCountDay;
+
+    /**
+     * 公众号内发消息人数。用户关注公众号后,在公众号对话框内发送消息的独立用户数。
+     */
+    @SerializedName("official_account_consult_count_hour")
+    private Long officialAccountConsultCountHour;
+
+    /**
+     * 公众号内发消息人数。用户关注公众号后,在公众号对话框内发送消息的独立用户数。
+     */
+    @SerializedName("official_account_consult_count_minute")
+    private Long officialAccountConsultCountMinute;
+
+    /**
+     * 阅读粉丝量。近3日新增的粉丝中产生阅读行为的用户数。
+     */
+    @SerializedName("official_account_reader_count_total")
+    private Long officialAccountReaderCountTotal;
+
+    /**
+     * 阅读粉丝量。近3日新增的粉丝中产生阅读行为的用户数。
+     */
+    @SerializedName("official_account_reader_count_day")
+    private Long officialAccountReaderCountDay;
+
+    /**
+     * 阅读粉丝量。近3日新增的粉丝中产生阅读行为的用户数。
+     */
+    @SerializedName("official_account_reader_count_hour")
+    private Long officialAccountReaderCountHour;
+
+    /**
+     * 阅读粉丝量。近3日新增的粉丝中产生阅读行为的用户数。
+     */
+    @SerializedName("official_account_reader_count_minute")
+    private Long officialAccountReaderCountMinute;
+
+    /**
+     * 公众号内进件人数。在公众号内完整提交贷款申请资料的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_credit_apply_user_count_total")
+    private Long officialAccountCreditApplyUserCountTotal;
+
+    /**
+     * 公众号内进件人数。在公众号内完整提交贷款申请资料的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_credit_apply_user_count_day")
+    private Long officialAccountCreditApplyUserCountDay;
+
+    /**
+     * 公众号内进件人数。在公众号内完整提交贷款申请资料的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_credit_apply_user_count_hour")
+    private Long officialAccountCreditApplyUserCountHour;
+
+    /**
+     * 公众号内进件人数。在公众号内完整提交贷款申请资料的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_credit_apply_user_count_minute")
+    private Long officialAccountCreditApplyUserCountMinute;
+
+    /**
+     * 公众号内授信人数。在公众号内完整提交贷款申请资料,并通过放款方的资质审核的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_credit_user_count_total")
+    private Long officialAccountCreditUserCountTotal;
+
+    /**
+     * 公众号内授信人数。在公众号内完整提交贷款申请资料,并通过放款方的资质审核的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_credit_user_count_day")
+    private Long officialAccountCreditUserCountDay;
+
+    /**
+     * 公众号内授信人数。在公众号内完整提交贷款申请资料,并通过放款方的资质审核的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_credit_user_count_hour")
+    private Long officialAccountCreditUserCountHour;
+
+    /**
+     * 公众号内授信人数。在公众号内完整提交贷款申请资料,并通过放款方的资质审核的独立用户数。接入转化跟踪后可统计。
+     */
+    @SerializedName("official_account_credit_user_count_minute")
+    private Long officialAccountCreditUserCountMinute;
+
+    /**
+     * 广告分享次数。用户将广告落地页分享给好友和朋友圈的次数。
+     */
+    @SerializedName("forward_count_total")
+    private Long forwardCountTotal;
+
+    /**
+     * 广告分享次数。用户将广告落地页分享给好友和朋友圈的次数。
+     */
+    @SerializedName("forward_count_day")
+    private Long forwardCountDay;
+
+    /**
+     * 广告分享次数。用户将广告落地页分享给好友和朋友圈的次数。
+     */
+    @SerializedName("forward_count_hour")
+    private Long forwardCountHour;
+
+    /**
+     * 广告分享次数。用户将广告落地页分享给好友和朋友圈的次数。
+     */
+    @SerializedName("forward_count_minute")
+    private Long forwardCountMinute;
+
+    /**
+     * 广告分享人数。将广告落地页分享给好友和朋友圈的独立用户数。
+     */
+    @SerializedName("forward_user_count_total")
+    private Long forwardUserCountTotal;
+
+    /**
+     * 广告分享人数。将广告落地页分享给好友和朋友圈的独立用户数。
+     */
+    @SerializedName("forward_user_count_day")
+    private Long forwardUserCountDay;
+
+    /**
+     * 广告分享人数。将广告落地页分享给好友和朋友圈的独立用户数。
+     */
+    @SerializedName("forward_user_count_hour")
+    private Long forwardUserCountHour;
+
+    /**
+     * 广告分享人数。将广告落地页分享给好友和朋友圈的独立用户数。
+     */
+    @SerializedName("forward_user_count_minute")
+    private Long forwardUserCountMinute;
+
+    /**
+     * 不感兴趣点击次数。用户点击“不感兴趣”的次数。
+     */
+    @SerializedName("no_interest_count_total")
+    private Long noInterestCountTotal;
+
+    /**
+     * 不感兴趣点击次数。用户点击“不感兴趣”的次数。
+     */
+    @SerializedName("no_interest_count_day")
+    private Long noInterestCountDay;
+
+    /**
+     * 不感兴趣点击次数。用户点击“不感兴趣”的次数。
+     */
+    @SerializedName("no_interest_count_hour")
+    private Long noInterestCountHour;
+
+    /**
+     * 不感兴趣点击次数。用户点击“不感兴趣”的次数。
+     */
+    @SerializedName("no_interest_count_minute")
+    private Long noInterestCountMinute;
+
+    /**
+     * 聚合
+     *
+     * @param beforeYesterdayDayDWD 广告前天(包括前天)聚合的天数据(用来计算总数据)
+     * @param yesterdayMinuteDWD    昨天聚合的数据
+     * @param todayODSList          当前待聚合的数据(当天数据列表)
+     * @param statODS
+     * @return
+     */
+    public static PlanStatOfMinuteDWD reduce(PlanStatOfDayDWD beforeYesterdayDayDWD, PlanStatOfMinuteDWD yesterdayMinuteDWD, List<AdDataOfMinuteODS> todayODSList, AdDataOfMinuteODS statODS, PlanStatOfMinuteDWD lastMinuteDWD, long createTime) {
+        PlanStatOfMinuteDWD result = new PlanStatOfMinuteDWD();
+        BeanUtils.copyProperties(statODS, result);
+        result = initValue(result);
+        result.setCreateTime(new Date(createTime));
+
+        Map<Long, Set<Long>> adGroupMap = new HashMap<>();
+        Set<Long> adIdsTemp = new HashSet<>(3);
+        adIdsTemp.add(statODS.getAdId());
+        adGroupMap.put(statODS.getAdgroupId(), adIdsTemp);
+        result.setAdGroupMap(adGroupMap);
+
+        Set<Long> adIds = new HashSet<>(3);
+        adIds.add(statODS.getAdId());
+        result.setAdIds(adIds);
+
+        // 填充小时数据
+        result.setCostDeviationRateHour(statODS.getCostDeviationRate());
+        result.setCostHour(statODS.getCost());
+        result.setCompensationAmountHour(statODS.getCompensationAmount());
+        result.setViewCountHour(statODS.getViewCount());
+        result.setThousandDisplayPriceHour(statODS.getThousandDisplayPrice());
+        result.setAvgViewPerUserHour(statODS.getAvgViewPerUser());
+        result.setValidClickCountHour(statODS.getValidClickCount());
+        result.setCtrHour(statODS.getCtr());
+        result.setCpcHour(statODS.getCpc());
+        result.setValuableClickCountHour(statODS.getValuableClickCount());
+        result.setValuableClickRateHour(statODS.getValuableClickRate());
+        result.setValuableClickCostHour(statODS.getValuableClickCost());
+        result.setConversionsCountHour(statODS.getConversionsCount());
+        result.setConversionsCostHour(statODS.getConversionsCost());
+        result.setConversionsRateHour(statODS.getConversionsRate());
+        result.setDeepConversionsCountHour(statODS.getDeepConversionsCount());
+        result.setDeepConversionsCostHour(statODS.getDeepConversionsCost());
+        result.setDeepConversionsRateHour(statODS.getDeepConversionsRate());
+        result.setOrderCountHour(statODS.getOrderCount());
+        result.setFirstDayOrderCountHour(statODS.getFirstDayOrderCount());
+        result.setWebOrderCostHour(statODS.getWebOrderCost());
+        result.setOrderRateHour(statODS.getOrderRate());
+        result.setOrderAmountHour(statODS.getOrderAmount());
+        result.setFirstDayOrderAmountHour(statODS.getFirstDayOrderAmount());
+        result.setOrderUnitPriceHour(statODS.getOrderUnitPrice());
+        result.setOrderRoiHour(statODS.getOrderRoi());
+        result.setSignInCountHour(statODS.getSignInCount());
+        result.setScanFollowCountHour(statODS.getScanFollowCount());
+        result.setWechatAppRegisterUvHour(statODS.getWechatAppRegisterUv());
+        result.setWechatMinigameRegisterCostHour(statODS.getWechatMinigameRegisterCost());
+        result.setWechatMinigameRegisterRateHour(statODS.getWechatMinigameRegisterRate());
+        result.setWechatMinigameArpuHour(statODS.getWechatMinigameArpu());
+        result.setWechatMinigameRetentionCountHour(statODS.getWechatMinigameRetentionCount());
+        result.setWechatMinigameCheckoutCountHour(statODS.getWechatMinigameCheckoutCount());
+        result.setWechatMinigameCheckoutAmountHour(statODS.getWechatMinigameCheckoutAmount());
+        result.setOfficialAccountFollowCountHour(statODS.getOfficialAccountFollowCount());
+        result.setOfficialAccountFollowRateHour(statODS.getOfficialAccountFollowRate());
+        result.setOfficialAccountRegisterUserCountHour(statODS.getOfficialAccountRegisterUserCount());
+        result.setOfficialAccountRegisterRateHour(statODS.getOfficialAccountRegisterRate());
+        result.setOfficialAccountRegisterCostHour(statODS.getOfficialAccountRegisterCost());
+        result.setOfficialAccountRegisterAmountHour(statODS.getOfficialAccountRegisterAmount());
+        result.setOfficialAccountRegisterRoiHour(statODS.getOfficialAccountRegisterRoi());
+        result.setOfficialAccountApplyCountHour(statODS.getOfficialAccountApplyCount());
+        result.setOfficialAccountApplyUserCountHour(statODS.getOfficialAccountApplyUserCount());
+        result.setOfficialAccountApplyRateHour(statODS.getOfficialAccountApplyRate());
+        result.setOfficialAccountApplyCostHour(statODS.getOfficialAccountApplyCost());
+        result.setOfficialAccountApplyAmountHour(statODS.getOfficialAccountApplyAmount());
+        result.setOfficialAccountApplyRoiHour(statODS.getOfficialAccountApplyRoi());
+        result.setOfficialAccountOrderCountHour(statODS.getOfficialAccountOrderCount());
+        result.setOfficialAccountFirstDayOrderCountHour(statODS.getOfficialAccountFirstDayOrderCount());
+        result.setOfficialAccountOrderUserCountHour(statODS.getOfficialAccountOrderUserCount());
+        result.setOfficialAccountOrderRateHour(statODS.getOfficialAccountOrderRate());
+        result.setOfficialAccountOrderCostHour(statODS.getOfficialAccountOrderCost());
+        result.setOfficialAccountOrderAmountHour(statODS.getOfficialAccountOrderAmount());
+        result.setOfficialAccountFirstDayOrderAmountHour(statODS.getOfficialAccountFirstDayOrderAmount());
+        result.setOfficialAccountOrderRoiHour(statODS.getOfficialAccountOrderRoi());
+        result.setOfficialAccountConsultCountHour(statODS.getOfficialAccountConsultCount());
+        result.setOfficialAccountReaderCountHour(statODS.getOfficialAccountReaderCount());
+        result.setOfficialAccountCreditApplyUserCountHour(statODS.getOfficialAccountCreditApplyUserCount());
+        result.setOfficialAccountCreditUserCountHour(statODS.getOfficialAccountCreditUserCount());
+        result.setForwardCountHour(statODS.getForwardCount());
+        result.setForwardUserCountHour(statODS.getForwardUserCount());
+        result.setNoInterestCountHour(statODS.getNoInterestCount());
+        // 聚合当天的数据
+        for (AdDataOfMinuteODS todayODS : todayODSList) {
+            result.getAdIds().add(todayODS.getAdId());
+            result.getAdGroupMap().computeIfAbsent(todayODS.getAdgroupId(), key -> new HashSet<>(3)).add(todayODS.getAdId());
+
+            result.setCostDeviationRateDay(result.getCostDeviationRateDay() + todayODS.getCostDeviationRate());
+            result.setCostDay(result.getCostDay() + todayODS.getCost());
+            result.setCompensationAmountDay(result.getCompensationAmountDay() + todayODS.getCompensationAmount());
+            result.setViewCountDay(result.getViewCountDay() + todayODS.getViewCount());
+            // 总消耗 / 总曝光
+            result.setThousandDisplayPriceDay(result.getViewCountDay() == 0 ? 0 : (result.getCostDay() / result.getViewCountDay() * 1000));
+            result.setValidClickCountDay(result.getValidClickCountDay() + todayODS.getValidClickCount());
+            // 广告点击次数 / 广告曝光次数
+            result.setCtrDay(result.getViewCountDay() == 0 ? 0.0 : result.getValidClickCountDay() / result.getViewCountDay());
+            // 广告花费/广告点击次数
+            result.setCpcDay(result.getValidClickCountDay() == 0 ? 0 : result.getCostDay() / result.getValidClickCountDay());
+            result.setValuableClickCountDay(result.getValuableClickCountDay() + todayODS.getValuableClickCount());
+            // 广告可转化点击次数/广告曝光次数
+            result.setValuableClickRateDay(result.getViewCountDay() == 0 ? 0.0 : result.getValuableClickCountDay() / result.getViewCountDay());
+            // 广告花费/可转化点击次数
+            result.setValuableClickCostDay(result.getValuableClickCountDay() == 0 ? 0 : result.getCostDay() / result.getValuableClickCountDay());
+            result.setConversionsCountDay(result.getConversionsCountDay() + todayODS.getConversionsCount());
+            // 广告花费/转化目标量
+            result.setConversionsCostDay(result.getConversionsCountDay() == 0 ? 0 : result.getCostDay() / result.getConversionsCountDay());
+            // 公众号:转化目标量/点击次数。
+            result.setConversionsRateDay(result.getValidClickCountDay() == 0 ? 0.0 : result.getConversionsCountDay() / result.getValidClickCountDay());
+            result.setDeepConversionsCountDay(result.getDeepConversionsCountDay() + todayODS.getDeepConversionsCount());
+            // 广告花费/深度转化目标量
+            result.setDeepConversionsCostDay(result.getDeepConversionsCountDay() == 0 ? 0 : result.getCostDay() / result.getDeepConversionsCountDay());
+            // 深度转化目标量/可转化点击次数
+            result.setDeepConversionsRateDay(result.getValuableClickCountDay() == 0 ? 0.0 : result.getDeepConversionsCountDay() / result.getValuableClickCountDay());
+            result.setOrderCountDay(result.getOrderCountDay() + todayODS.getOrderCount());
+            result.setFirstDayOrderCountDay(result.getFirstDayOrderCountDay() + todayODS.getFirstDayOrderCount());
+            // 广告花费/下单量
+            result.setWebOrderCostDay(result.getOrderCountDay() == 0 ? 0 : result.getCostDay() / result.getOrderCountDay());
+            // 下单量/点击次数
+            result.setOrderRateDay(result.getValidClickCountDay() == 0 ? 0.0 : result.getOrderCountDay() / result.getValidClickCountDay());
+            result.setOrderAmountDay(result.getOrderAmountDay() + todayODS.getOrderAmount());
+            result.setFirstDayOrderAmountDay(result.getFirstDayOrderAmountDay() + todayODS.getFirstDayOrderAmount());
+            // 下单金额/下单量
+            result.setOrderUnitPriceDay(result.getOrderCountDay() == 0 ? 0 : result.getOrderAmountDay() / result.getOrderCountDay());
+            // 下单金额/广告花费
+            result.setOrderRoiDay(result.getCostDay() == 0 ? 0.0 : result.getOrderAmountDay() / result.getCostDay());
+            result.setSignInCountDay(result.getSignInCountDay() + todayODS.getSignInCount());
+            result.setScanFollowCountDay(result.getScanFollowCountDay() + todayODS.getScanFollowCount());
+            result.setWechatAppRegisterUvDay(result.getWechatAppRegisterUvDay() + todayODS.getWechatAppRegisterUv());
+            // 广告消耗 / 小游戏注册人数
+            result.setWechatMinigameRegisterCostDay(result.getWechatAppRegisterUvDay() == 0 ? 0 : result.getCostDay() / result.getWechatAppRegisterUvDay());
+            // 小游戏注册人数 / 广告点击次数
+            result.setWechatMinigameRegisterRateDay(result.getValidClickCountDay() == 0 ? 0.0 : result.getWechatAppRegisterUvDay() / result.getValidClickCountDay());
+            // 总收益 / 总人数
+            result.setWechatMinigameArpuDay(result.getWechatAppRegisterUvDay() == 0 ? 0.0 : result.getOrderAmountDay() / result.getWechatAppRegisterUvDay());
+            result.setWechatMinigameRetentionCountDay(result.getWechatMinigameRetentionCountDay() + todayODS.getWechatMinigameRetentionCount());
+            result.setWechatMinigameCheckoutCountDay(result.getWechatMinigameCheckoutCountDay() + todayODS.getWechatMinigameCheckoutCount());
+            result.setWechatMinigameCheckoutAmountDay(result.getWechatMinigameCheckoutAmountDay() + todayODS.getWechatMinigameCheckoutAmount());
+            result.setOfficialAccountFollowCountDay(result.getOfficialAccountFollowCountDay() + todayODS.getOfficialAccountFollowCount());
+            // 关注次数 / 点击次数
+            result.setOfficialAccountFollowRateDay(result.getValidClickCountDay() == 0 ? 0.0 : result.getOfficialAccountFollowCountDay() / result.getValidClickCountDay());
+            result.setOfficialAccountRegisterUserCountDay(result.getOfficialAccountRegisterUserCountDay() + todayODS.getOfficialAccountRegisterUserCount());
+            // 公众号内注册人数 / 公众号关注次数
+            result.setOfficialAccountRegisterRateDay(result.getOfficialAccountFollowCountDay() == 0 ? 0.0 : result.getOfficialAccountRegisterUserCountDay() / result.getOfficialAccountFollowCountDay());
+            // 广告消耗 / 广告注册人数
+            result.setOfficialAccountRegisterCostDay(result.getOfficialAccountRegisterUserCountDay() == 0 ? 0 : result.getCostDay() / result.getOfficialAccountRegisterUserCountDay());
+            result.setOfficialAccountRegisterAmountDay(result.getOfficialAccountRegisterAmountDay() + todayODS.getOfficialAccountRegisterAmount());
+            // 注册产生的订单金额累计/广告花费
+            result.setOfficialAccountRegisterRoiDay(result.getCostDay() == 0 ? 0 : result.getOfficialAccountRegisterAmountDay() / result.getCostDay());
+            result.setOfficialAccountApplyCountDay(result.getOfficialAccountApplyCountDay() + todayODS.getOfficialAccountApplyCount());
+            result.setOfficialAccountApplyUserCountDay(result.getOfficialAccountApplyUserCountDay() + todayODS.getOfficialAccountApplyUserCount());
+            // 公众号内填单的独立用户数/公众号关注次数
+            result.setOfficialAccountApplyRateDay(result.getOfficialAccountFollowCountDay() == 0 ? 0.0 : result.getOfficialAccountApplyUserCountDay() / result.getOfficialAccountFollowCountDay());
+            // 广告花费/广告产生的填单行为数量
+            result.setOfficialAccountApplyCostDay(result.getOfficialAccountApplyUserCountDay() == 0 ? 0 : result.getCostDay() / result.getOfficialAccountApplyUserCountDay());
+            result.setOfficialAccountApplyAmountDay(result.getOfficialAccountApplyAmountDay() + todayODS.getOfficialAccountApplyAmount());
+            // 填单产生的订单金额累计/广告花费
+            result.setOfficialAccountApplyRoiDay(result.getCostDay() == 0 ? 0 : result.getOfficialAccountApplyAmountDay() / result.getCostDay());
+            result.setOfficialAccountOrderCountDay(result.getOfficialAccountOrderCountDay() + todayODS.getOfficialAccountOrderCount());
+            result.setOfficialAccountFirstDayOrderCountDay(result.getOfficialAccountFirstDayOrderCountDay() + todayODS.getOfficialAccountFirstDayOrderCount());
+            result.setOfficialAccountOrderUserCountDay(result.getOfficialAccountOrderUserCountDay() + todayODS.getOfficialAccountOrderUserCount());
+            // 公众号内下单独立用户数(UV)/公众号关注次数
+            result.setOfficialAccountOrderRateDay(result.getOfficialAccountFollowCountDay() == 0 ? 0.0 : result.getOfficialAccountOrderUserCountDay() / result.getOfficialAccountFollowCountDay());
+            // 广告花费/广告产生的下单行为数量
+            result.setOfficialAccountOrderCostDay(result.getOfficialAccountOrderCountDay() == 0 ? 0 : result.getCostDay() / result.getOfficialAccountOrderCountDay());
+            result.setOfficialAccountOrderAmountDay(result.getOfficialAccountOrderAmountDay() + todayODS.getOfficialAccountOrderAmount());
+            result.setOfficialAccountFirstDayOrderAmountDay(result.getOfficialAccountFirstDayOrderAmountDay() + todayODS.getOfficialAccountFirstDayOrderAmount());
+            // 下单产生的订单金额累计/广告花费
+            result.setOfficialAccountOrderRoiDay(result.getCostDay() == 0 ? 0 : result.getOfficialAccountOrderAmountDay() / result.getCostDay());
+            result.setOfficialAccountConsultCountDay(result.getOfficialAccountConsultCountDay() + todayODS.getOfficialAccountConsultCount());
+            result.setOfficialAccountReaderCountDay(result.getOfficialAccountReaderCountDay() + todayODS.getOfficialAccountReaderCount());
+            result.setOfficialAccountCreditApplyUserCountDay(result.getOfficialAccountCreditApplyUserCountDay() + todayODS.getOfficialAccountCreditApplyUserCount());
+            result.setOfficialAccountCreditUserCountDay(result.getOfficialAccountCreditUserCountDay() + todayODS.getOfficialAccountCreditUserCount());
+            result.setForwardCountDay(result.getForwardCountDay() + todayODS.getForwardCount());
+            result.setForwardUserCountDay(result.getForwardUserCountDay() + todayODS.getForwardUserCount());
+            result.setNoInterestCountDay(result.getNoInterestCountDay() + todayODS.getNoInterestCount());
+        }
+        // 填充总数据
+        if (beforeYesterdayDayDWD != null) {
+            result.getAdIds().addAll(beforeYesterdayDayDWD.getAdIds());
+            PlanUtil.reduceAdGroup(result.getAdGroupMap(), beforeYesterdayDayDWD.getAdGroupMap());
+
+            result.setCostDeviationRateTotal(beforeYesterdayDayDWD.getCostDeviationRateTotal());
+            result.setCostTotal(beforeYesterdayDayDWD.getCostTotal());
+            result.setCompensationAmountTotal(beforeYesterdayDayDWD.getCompensationAmountTotal());
+            result.setViewCountTotal(beforeYesterdayDayDWD.getViewCountTotal());
+            result.setThousandDisplayPriceAll(beforeYesterdayDayDWD.getThousandDisplayPriceAll());
+            result.setAvgViewPerUserAll(beforeYesterdayDayDWD.getAvgViewPerUserAll());
+            result.setValidClickCountTotal(beforeYesterdayDayDWD.getValidClickCountTotal());
+            result.setCtrAll(beforeYesterdayDayDWD.getCtrAll());
+            result.setCpcAll(beforeYesterdayDayDWD.getCpcAll());
+            result.setValuableClickCountTotal(beforeYesterdayDayDWD.getValuableClickCountTotal());
+            result.setValuableClickRateAll(beforeYesterdayDayDWD.getValuableClickRateAll());
+            result.setValuableClickCostAll(beforeYesterdayDayDWD.getValuableClickCostAll());
+            result.setConversionsCountTotal(beforeYesterdayDayDWD.getConversionsCountTotal());
+            result.setConversionsCostAll(beforeYesterdayDayDWD.getConversionsCostAll());
+            result.setConversionsRateAll(beforeYesterdayDayDWD.getConversionsRateAll());
+            result.setDeepConversionsCountTotal(beforeYesterdayDayDWD.getDeepConversionsCountTotal());
+            result.setDeepConversionsCostAll(beforeYesterdayDayDWD.getDeepConversionsCostAll());
+            result.setDeepConversionsRateAll(beforeYesterdayDayDWD.getDeepConversionsRateAll());
+            result.setOrderCountTotal(beforeYesterdayDayDWD.getOrderCountTotal());
+            result.setFirstDayOrderCountTotal(beforeYesterdayDayDWD.getFirstDayOrderCountTotal());
+            result.setWebOrderCostAll(beforeYesterdayDayDWD.getWebOrderCostAll());
+            result.setOrderRateAll(beforeYesterdayDayDWD.getOrderRateAll());
+            result.setOrderAmountTotal(beforeYesterdayDayDWD.getOrderAmountTotal());
+            result.setFirstDayOrderAmountTotal(beforeYesterdayDayDWD.getFirstDayOrderAmountTotal());
+            result.setOrderUnitPriceAll(beforeYesterdayDayDWD.getOrderUnitPriceAll());
+            result.setOrderRoiAll(beforeYesterdayDayDWD.getOrderRoiAll());
+            result.setSignInCountTotal(beforeYesterdayDayDWD.getSignInCountTotal());
+            result.setScanFollowCountTotal(beforeYesterdayDayDWD.getScanFollowCountTotal());
+            result.setWechatAppRegisterUvTotal(beforeYesterdayDayDWD.getWechatAppRegisterUvTotal());
+            result.setWechatMinigameRegisterCostAll(beforeYesterdayDayDWD.getWechatMinigameRegisterCostAll());
+            result.setWechatMinigameRegisterRateAll(beforeYesterdayDayDWD.getWechatMinigameRegisterRateAll());
+            result.setWechatMinigameArpuAll(beforeYesterdayDayDWD.getWechatMinigameArpuAll());
+            result.setWechatMinigameRetentionCountTotal(beforeYesterdayDayDWD.getWechatMinigameRetentionCountTotal());
+            result.setWechatMinigameCheckoutCountTotal(beforeYesterdayDayDWD.getWechatMinigameCheckoutCountTotal());
+            result.setWechatMinigameCheckoutAmountTotal(beforeYesterdayDayDWD.getWechatMinigameCheckoutAmountTotal());
+            result.setOfficialAccountFollowCountTotal(beforeYesterdayDayDWD.getOfficialAccountFollowCountTotal());
+            result.setOfficialAccountFollowRateAll(beforeYesterdayDayDWD.getOfficialAccountFollowRateAll());
+            result.setOfficialAccountRegisterUserCountTotal(beforeYesterdayDayDWD.getOfficialAccountRegisterUserCountTotal());
+            result.setOfficialAccountRegisterRateAll(beforeYesterdayDayDWD.getOfficialAccountRegisterRateAll());
+            result.setOfficialAccountRegisterCostAll(beforeYesterdayDayDWD.getOfficialAccountRegisterCostAll());
+            result.setOfficialAccountRegisterAmountTotal(beforeYesterdayDayDWD.getOfficialAccountRegisterAmountTotal());
+            result.setOfficialAccountRegisterRoiAll(beforeYesterdayDayDWD.getOfficialAccountRegisterRoiAll());
+            result.setOfficialAccountApplyCountTotal(beforeYesterdayDayDWD.getOfficialAccountApplyCountTotal());
+            result.setOfficialAccountApplyUserCountTotal(beforeYesterdayDayDWD.getOfficialAccountApplyUserCountTotal());
+            result.setOfficialAccountApplyRateAll(beforeYesterdayDayDWD.getOfficialAccountApplyRateAll());
+            result.setOfficialAccountApplyCostAll(beforeYesterdayDayDWD.getOfficialAccountApplyCostAll());
+            result.setOfficialAccountApplyAmountTotal(beforeYesterdayDayDWD.getOfficialAccountApplyAmountTotal());
+            result.setOfficialAccountApplyRoiAll(beforeYesterdayDayDWD.getOfficialAccountApplyRoiAll());
+            result.setOfficialAccountOrderCountTotal(beforeYesterdayDayDWD.getOfficialAccountOrderCountTotal());
+            result.setOfficialAccountFirstDayOrderCountTotal(beforeYesterdayDayDWD.getOfficialAccountFirstDayOrderCountTotal());
+            result.setOfficialAccountOrderUserCountTotal(beforeYesterdayDayDWD.getOfficialAccountOrderUserCountTotal());
+            result.setOfficialAccountOrderRateAll(beforeYesterdayDayDWD.getOfficialAccountOrderRateAll());
+            result.setOfficialAccountOrderCostAll(beforeYesterdayDayDWD.getOfficialAccountOrderCostAll());
+            result.setOfficialAccountOrderAmountTotal(beforeYesterdayDayDWD.getOfficialAccountOrderAmountTotal());
+            result.setOfficialAccountFirstDayOrderAmountTotal(beforeYesterdayDayDWD.getOfficialAccountFirstDayOrderAmountTotal());
+            result.setOfficialAccountOrderRoiAll(beforeYesterdayDayDWD.getOfficialAccountOrderRoiAll());
+            result.setOfficialAccountConsultCountTotal(beforeYesterdayDayDWD.getOfficialAccountConsultCountTotal());
+            result.setOfficialAccountReaderCountTotal(beforeYesterdayDayDWD.getOfficialAccountReaderCountTotal());
+            result.setOfficialAccountCreditApplyUserCountTotal(beforeYesterdayDayDWD.getOfficialAccountCreditApplyUserCountTotal());
+            result.setOfficialAccountCreditUserCountTotal(beforeYesterdayDayDWD.getOfficialAccountCreditUserCountTotal());
+            result.setForwardCountTotal(beforeYesterdayDayDWD.getForwardCountTotal());
+            result.setForwardUserCountTotal(beforeYesterdayDayDWD.getForwardUserCountTotal());
+            result.setNoInterestCountTotal(beforeYesterdayDayDWD.getNoInterestCountTotal());
+        }
+        if (yesterdayMinuteDWD != null) {
+            result.getAdIds().addAll(yesterdayMinuteDWD.getAdIds());
+            PlanUtil.reduceAdGroup(result.getAdGroupMap(), yesterdayMinuteDWD.getAdGroupMap());
+
+            result.setCostDeviationRateTotal(result.getCostDeviationRateTotal() + yesterdayMinuteDWD.getCostDeviationRateTotal());
+            result.setCostTotal(result.getCostTotal() + yesterdayMinuteDWD.getCostTotal());
+            result.setCompensationAmountTotal(result.getCompensationAmountTotal() + yesterdayMinuteDWD.getCompensationAmountTotal());
+            result.setViewCountTotal(result.getViewCountTotal() + yesterdayMinuteDWD.getCompensationAmountTotal());
+            result.setValidClickCountTotal(result.getValidClickCountTotal() + yesterdayMinuteDWD.getValidClickCountTotal());
+            result.setValuableClickCountTotal(result.getValuableClickCountTotal() + yesterdayMinuteDWD.getValuableClickCountTotal());
+            result.setConversionsCountTotal(result.getConversionsCountTotal() + yesterdayMinuteDWD.getConversionsCountTotal());
+            result.setDeepConversionsCountTotal(result.getDeepConversionsCountTotal() + yesterdayMinuteDWD.getDeepConversionsCountTotal());
+            result.setOrderCountTotal(result.getOrderCountTotal() + yesterdayMinuteDWD.getOrderCountTotal());
+            result.setFirstDayOrderCountTotal(result.getFirstDayOrderCountTotal() + yesterdayMinuteDWD.getFirstDayOrderCountTotal());
+            result.setOrderAmountTotal(result.getOrderAmountTotal() + yesterdayMinuteDWD.getOrderAmountTotal());
+            result.setFirstDayOrderAmountTotal(result.getFirstDayOrderAmountTotal() + yesterdayMinuteDWD.getFirstDayOrderAmountTotal());
+            result.setSignInCountTotal(result.getSignInCountTotal() + yesterdayMinuteDWD.getSignInCountTotal());
+            result.setScanFollowCountTotal(result.getScanFollowCountTotal() + yesterdayMinuteDWD.getScanFollowCountTotal());
+            result.setWechatAppRegisterUvTotal(result.getWechatAppRegisterUvTotal() + yesterdayMinuteDWD.getWechatAppRegisterUvTotal());
+            result.setWechatMinigameRetentionCountTotal(result.getWechatMinigameRetentionCountTotal() + yesterdayMinuteDWD.getWechatMinigameRetentionCountTotal());
+            result.setWechatMinigameCheckoutCountTotal(result.getWechatMinigameCheckoutCountTotal() + yesterdayMinuteDWD.getWechatMinigameCheckoutCountTotal());
+            result.setWechatMinigameCheckoutAmountTotal(result.getWechatMinigameCheckoutAmountTotal() + yesterdayMinuteDWD.getWechatMinigameCheckoutAmountTotal());
+            result.setOfficialAccountFollowCountTotal(result.getOfficialAccountFollowCountTotal() + yesterdayMinuteDWD.getOfficialAccountFollowCountTotal());
+            result.setOfficialAccountRegisterUserCountTotal(result.getOfficialAccountRegisterUserCountTotal() + yesterdayMinuteDWD.getOfficialAccountRegisterUserCountTotal());
+            result.setOfficialAccountRegisterAmountTotal(result.getOfficialAccountRegisterAmountTotal() + yesterdayMinuteDWD.getOfficialAccountRegisterAmountTotal());
+            result.setOfficialAccountApplyCountTotal(result.getOfficialAccountApplyCountTotal() + yesterdayMinuteDWD.getOfficialAccountApplyCountTotal());
+            result.setOfficialAccountApplyUserCountTotal(result.getOfficialAccountApplyUserCountTotal() + yesterdayMinuteDWD.getOfficialAccountApplyUserCountTotal());
+            result.setOfficialAccountApplyAmountTotal(result.getOfficialAccountApplyAmountTotal() + yesterdayMinuteDWD.getOfficialAccountApplyAmountTotal());
+            result.setOfficialAccountOrderCountTotal(result.getOfficialAccountOrderCountTotal() + yesterdayMinuteDWD.getOfficialAccountOrderCountTotal());
+            result.setOfficialAccountFirstDayOrderCountTotal(result.getOfficialAccountFirstDayOrderCountTotal() + yesterdayMinuteDWD.getOfficialAccountFirstDayOrderCountTotal());
+            result.setOfficialAccountOrderUserCountTotal(result.getOfficialAccountOrderUserCountTotal() + yesterdayMinuteDWD.getOfficialAccountOrderUserCountTotal());
+            result.setOfficialAccountOrderAmountTotal(result.getOfficialAccountOrderAmountTotal() + yesterdayMinuteDWD.getOfficialAccountOrderAmountTotal());
+            result.setOfficialAccountFirstDayOrderAmountTotal(result.getOfficialAccountFirstDayOrderAmountTotal() + yesterdayMinuteDWD.getOfficialAccountFirstDayOrderAmountTotal());
+            result.setOfficialAccountConsultCountTotal(result.getOfficialAccountConsultCountTotal() + yesterdayMinuteDWD.getOfficialAccountConsultCountTotal());
+            result.setOfficialAccountReaderCountTotal(result.getOfficialAccountReaderCountTotal() + yesterdayMinuteDWD.getOfficialAccountReaderCountTotal());
+            result.setOfficialAccountCreditApplyUserCountTotal(result.getOfficialAccountCreditApplyUserCountTotal() + yesterdayMinuteDWD.getOfficialAccountCreditApplyUserCountTotal());
+            result.setOfficialAccountCreditUserCountTotal(result.getOfficialAccountCreditUserCountTotal() + yesterdayMinuteDWD.getOfficialAccountCreditUserCountTotal());
+            result.setForwardCountTotal(result.getForwardCountTotal() + yesterdayMinuteDWD.getForwardCountTotal());
+            result.setForwardUserCountTotal(result.getForwardUserCountTotal() + yesterdayMinuteDWD.getForwardUserCountTotal());
+            result.setNoInterestCountTotal(result.getNoInterestCountTotal() + yesterdayMinuteDWD.getNoInterestCountTotal());
+        }
+        result.setCostDeviationRateTotal(result.getCostDeviationRateTotal() + result.getCostDeviationRateDay());
+        result.setCostTotal(result.getCostTotal() + result.getCostDay());
+        result.setCompensationAmountTotal(result.getCompensationAmountTotal() + result.getCompensationAmountDay());
+        result.setViewCountTotal(result.getViewCountTotal() + result.getCompensationAmountDay());
+        result.setValidClickCountTotal(result.getValidClickCountTotal() + result.getValidClickCountDay());
+        result.setValuableClickCountTotal(result.getValuableClickCountTotal() + result.getValuableClickCountDay());
+        result.setConversionsCountTotal(result.getConversionsCountTotal() + result.getConversionsCountDay());
+        result.setDeepConversionsCountTotal(result.getDeepConversionsCountTotal() + result.getDeepConversionsCountDay());
+        result.setOrderCountTotal(result.getOrderCountTotal() + result.getOrderCountDay());
+        result.setFirstDayOrderCountTotal(result.getFirstDayOrderCountTotal() + result.getFirstDayOrderCountDay());
+        result.setOrderAmountTotal(result.getOrderAmountTotal() + result.getOrderAmountDay());
+        result.setFirstDayOrderAmountTotal(result.getFirstDayOrderAmountTotal() + result.getFirstDayOrderAmountDay());
+        result.setSignInCountTotal(result.getSignInCountTotal() + result.getSignInCountDay());
+        result.setScanFollowCountTotal(result.getScanFollowCountTotal() + result.getScanFollowCountDay());
+        result.setWechatAppRegisterUvTotal(result.getWechatAppRegisterUvTotal() + result.getWechatAppRegisterUvDay());
+        result.setWechatMinigameRetentionCountTotal(result.getWechatMinigameRetentionCountTotal() + result.getWechatMinigameRetentionCountDay());
+        result.setWechatMinigameCheckoutCountTotal(result.getWechatMinigameCheckoutCountTotal() + result.getWechatMinigameCheckoutCountDay());
+        result.setWechatMinigameCheckoutAmountTotal(result.getWechatMinigameCheckoutAmountTotal() + result.getWechatMinigameCheckoutAmountDay());
+        result.setOfficialAccountFollowCountTotal(result.getOfficialAccountFollowCountTotal() + result.getOfficialAccountFollowCountDay());
+        result.setOfficialAccountRegisterUserCountTotal(result.getOfficialAccountRegisterUserCountTotal() + result.getOfficialAccountRegisterUserCountDay());
+        result.setOfficialAccountRegisterAmountTotal(result.getOfficialAccountRegisterAmountTotal() + result.getOfficialAccountRegisterAmountDay());
+        result.setOfficialAccountApplyCountTotal(result.getOfficialAccountApplyCountTotal() + result.getOfficialAccountApplyCountDay());
+        result.setOfficialAccountApplyUserCountTotal(result.getOfficialAccountApplyUserCountTotal() + result.getOfficialAccountApplyUserCountDay());
+        result.setOfficialAccountOrderCountTotal(result.getOfficialAccountOrderCountTotal() + result.getOfficialAccountOrderCountDay());
+        result.setOfficialAccountFirstDayOrderCountTotal(result.getOfficialAccountFirstDayOrderCountTotal() + result.getOfficialAccountFirstDayOrderCountDay());
+        result.setOfficialAccountOrderUserCountTotal(result.getOfficialAccountOrderUserCountTotal() + result.getOfficialAccountOrderUserCountDay());
+        result.setOfficialAccountApplyAmountTotal(result.getOfficialAccountApplyAmountTotal() + result.getOfficialAccountApplyAmountDay());
+        result.setOfficialAccountOrderAmountTotal(result.getOfficialAccountOrderAmountTotal() + result.getOfficialAccountOrderAmountDay());
+        result.setOfficialAccountFirstDayOrderAmountTotal(result.getOfficialAccountFirstDayOrderAmountTotal() + result.getOfficialAccountFirstDayOrderAmountDay());
+        result.setOfficialAccountConsultCountTotal(result.getOfficialAccountConsultCountTotal() + result.getOfficialAccountConsultCountDay());
+        result.setOfficialAccountReaderCountTotal(result.getOfficialAccountReaderCountTotal() + result.getOfficialAccountReaderCountDay());
+        result.setOfficialAccountCreditApplyUserCountTotal(result.getOfficialAccountCreditApplyUserCountTotal() + result.getOfficialAccountCreditApplyUserCountDay());
+        result.setOfficialAccountCreditUserCountTotal(result.getOfficialAccountCreditUserCountTotal() + result.getOfficialAccountCreditUserCountDay());
+        result.setForwardCountTotal(result.getForwardCountTotal() + result.getForwardCountDay());
+        result.setForwardUserCountTotal(result.getForwardUserCountTotal() + result.getForwardUserCountDay());
+        result.setNoInterestCountTotal(result.getNoInterestCountTotal() + result.getNoInterestCountDay());
+        // 总消耗 / 总曝光
+        result.setThousandDisplayPriceAll(result.getViewCountTotal() == 0 ? 0 : (result.getCostTotal() / result.getViewCountTotal() * 1000));
+        // 广告点击次数 / 广告曝光次数
+        result.setCtrAll(result.getViewCountTotal() == 0 ? 0.0 : result.getValidClickCountTotal() / result.getViewCountTotal());
+        // 广告花费/广告点击次数
+        result.setCpcAll(result.getValidClickCountTotal() == 0 ? 0 : result.getCostTotal() / result.getValidClickCountTotal());
+        // 广告可转化点击次数/广告曝光次数
+        result.setValuableClickRateAll(result.getViewCountTotal() == 0 ? 0.0 : result.getValuableClickCountTotal() / result.getViewCountTotal());
+        // 广告花费/可转化点击次数
+        result.setValuableClickCostAll(result.getValuableClickCountTotal() == 0 ? 0 : result.getCostTotal() / result.getValuableClickCountTotal());
+        // 广告花费/转化目标量
+        result.setConversionsCostAll(result.getConversionsCountTotal() == 0 ? 0 : result.getCostTotal() / result.getConversionsCountTotal());
+        // 公众号:转化目标量/点击次数。
+        result.setConversionsRateAll(result.getValidClickCountTotal() == 0 ? 0.0 : result.getConversionsCountTotal() / result.getValidClickCountTotal());
+        // 广告花费/深度转化目标量
+        result.setDeepConversionsCostAll(result.getDeepConversionsCountTotal() == 0 ? 0 : result.getCostTotal() / result.getDeepConversionsCountTotal());
+        // 深度转化目标量/可转化点击次数
+        result.setDeepConversionsRateAll(result.getValuableClickCountTotal() == 0 ? 0.0 : result.getDeepConversionsCountTotal() / result.getValuableClickCountTotal());
+        // 广告花费/下单量
+        result.setWebOrderCostAll(result.getOrderCountTotal() == 0 ? 0 : result.getCostTotal() / result.getOrderCountTotal());
+        // 下单量/点击次数
+        result.setOrderRateAll(result.getValidClickCountTotal() == 0 ? 0.0 : result.getOrderCountTotal() / result.getValidClickCountTotal());
+        // 下单金额/下单量
+        result.setOrderUnitPriceAll(result.getOrderCountTotal() == 0 ? 0 : result.getOrderAmountTotal() / result.getOrderCountTotal());
+        // 下单金额/广告花费
+        result.setOrderRoiAll(result.getCostTotal() == 0 ? 0.0 : result.getOrderAmountTotal() / result.getCostTotal());
+        // 广告消耗 / 小游戏注册人数
+        result.setWechatMinigameRegisterCostAll(result.getWechatAppRegisterUvTotal() == 0 ? 0 : result.getCostTotal() / result.getWechatAppRegisterUvTotal());
+        // 小游戏注册人数 / 广告点击次数
+        result.setWechatMinigameRegisterRateAll(result.getValidClickCountTotal() == 0 ? 0.0 : result.getWechatAppRegisterUvTotal() / result.getValidClickCountTotal());
+        // 总收益 / 总人数
+        result.setWechatMinigameArpuAll(result.getWechatAppRegisterUvTotal() == 0 ? 0.0 : result.getOrderAmountTotal() / result.getWechatAppRegisterUvTotal());
+        // 关注次数 / 点击次数
+        result.setOfficialAccountFollowRateAll(result.getValidClickCountTotal() == 0 ? 0.0 : result.getOfficialAccountFollowCountTotal() / result.getValidClickCountTotal());
+        // 公众号内注册人数 / 公众号关注次数
+        result.setOfficialAccountRegisterRateAll(result.getOfficialAccountFollowCountTotal() == 0 ? 0.0 : result.getOfficialAccountRegisterUserCountTotal() / result.getOfficialAccountFollowCountTotal());
+        // 广告消耗 / 广告注册人数
+        result.setOfficialAccountRegisterCostAll(result.getOfficialAccountRegisterUserCountTotal() == 0 ? 0 : result.getCostTotal() / result.getOfficialAccountRegisterUserCountTotal());
+        // 注册产生的订单金额累计/广告花费
+        result.setOfficialAccountRegisterRoiAll(result.getCostTotal() == 0 ? 0 : result.getOfficialAccountRegisterAmountTotal() / result.getCostTotal());
+        // 公众号内填单的独立用户数/公众号关注次数
+        result.setOfficialAccountApplyRateAll(result.getOfficialAccountFollowCountTotal() == 0 ? 0.0 : result.getOfficialAccountApplyUserCountTotal() / result.getOfficialAccountFollowCountTotal());
+        // 广告花费/广告产生的填单行为数量
+        result.setOfficialAccountApplyCostAll(result.getOfficialAccountApplyUserCountTotal() == 0 ? 0 : result.getCostTotal() / result.getOfficialAccountApplyUserCountTotal());
+        // 填单产生的订单金额累计/广告花费
+        result.setOfficialAccountApplyRoiAll(result.getCostTotal() == 0 ? 0 : result.getOfficialAccountApplyAmountTotal() / result.getCostTotal());
+        // 公众号内下单独立用户数(UV)/公众号关注次数
+        result.setOfficialAccountOrderRateAll(result.getOfficialAccountFollowCountTotal() == 0 ? 0.0 : result.getOfficialAccountOrderUserCountTotal() / result.getOfficialAccountFollowCountTotal());
+        // 广告花费/广告产生的下单行为数量
+        result.setOfficialAccountOrderCostAll(result.getOfficialAccountOrderCountTotal() == 0 ? 0 : result.getCostTotal() / result.getOfficialAccountOrderCountTotal());
+        // 下单产生的订单金额累计/广告花费
+        result.setOfficialAccountOrderRoiAll(result.getCostTotal() == 0 ? 0 : result.getOfficialAccountOrderAmountTotal() / result.getCostTotal());
+
+        // 上一次聚合的 5分钟数据
+        if (lastMinuteDWD != null) {
+            if (lastMinuteDWD.getHour().equals(result.getHour())) {
+                result.setCostDeviationRateMinute(result.getCostDeviationRateHour() - lastMinuteDWD.getCostDeviationRateHour());
+                result.setCostMinute(result.getCostHour() - lastMinuteDWD.getCostHour());
+                result.setCompensationAmountMinute(result.getCompensationAmountHour() - lastMinuteDWD.getCompensationAmountHour());
+                result.setViewCountMinute(result.getViewCountHour() - lastMinuteDWD.getViewCountHour());
+                result.setThousandDisplayPriceMinute(result.getThousandDisplayPriceHour() - lastMinuteDWD.getThousandDisplayPriceHour());
+                result.setAvgViewPerUserMinute(result.getAvgViewPerUserHour() - lastMinuteDWD.getAvgViewPerUserHour());
+                result.setValidClickCountMinute(result.getValidClickCountHour() - lastMinuteDWD.getValidClickCountHour());
+                result.setCtrMinute(result.getCtrHour() - lastMinuteDWD.getCtrHour());
+                result.setCpcMinute(result.getCpcHour() - lastMinuteDWD.getCpcHour());
+                result.setValuableClickCountMinute(result.getValuableClickCountHour() - lastMinuteDWD.getValuableClickCountHour());
+                result.setValuableClickRateMinute(result.getValuableClickRateHour() - lastMinuteDWD.getValuableClickRateHour());
+                result.setValuableClickCostMinute(result.getValuableClickCostHour() - lastMinuteDWD.getValuableClickCostHour());
+                result.setConversionsCountMinute(result.getConversionsCountHour() - lastMinuteDWD.getConversionsCountHour());
+                result.setConversionsCostMinute(result.getConversionsCostHour() - lastMinuteDWD.getConversionsCostHour());
+                result.setConversionsRateMinute(result.getConversionsRateHour() - lastMinuteDWD.getConversionsRateHour());
+                result.setDeepConversionsCountMinute(result.getDeepConversionsCountHour() - lastMinuteDWD.getDeepConversionsCountHour());
+                result.setDeepConversionsCostMinute(result.getDeepConversionsCostHour() - lastMinuteDWD.getDeepConversionsCostHour());
+                result.setDeepConversionsRateMinute(result.getDeepConversionsRateHour() - lastMinuteDWD.getDeepConversionsRateHour());
+                result.setOrderCountMinute(result.getOrderCountHour() - lastMinuteDWD.getOrderCountHour());
+                result.setFirstDayOrderCountMinute(result.getFirstDayOrderCountHour() - lastMinuteDWD.getFirstDayOrderCountHour());
+                result.setWebOrderCostMinute(result.getWebOrderCostHour() - lastMinuteDWD.getWebOrderCostHour());
+                result.setOrderRateMinute(result.getOrderRateHour() - lastMinuteDWD.getOrderRateHour());
+                result.setOrderAmountMinute(result.getOrderAmountHour() - lastMinuteDWD.getOrderAmountHour());
+                result.setFirstDayOrderAmountMinute(result.getFirstDayOrderAmountHour() - lastMinuteDWD.getFirstDayOrderAmountHour());
+                result.setOrderUnitPriceMinute(result.getOrderUnitPriceHour() - lastMinuteDWD.getOrderUnitPriceHour());
+                result.setOrderRoiMinute(result.getOrderRoiHour() - lastMinuteDWD.getOrderRoiHour());
+                result.setSignInCountMinute(result.getSignInCountHour() - lastMinuteDWD.getSignInCountHour());
+                result.setScanFollowCountMinute(result.getScanFollowCountHour() - lastMinuteDWD.getScanFollowCountHour());
+                result.setWechatAppRegisterUvMinute(result.getWechatAppRegisterUvHour() - lastMinuteDWD.getWechatAppRegisterUvHour());
+                result.setWechatMinigameRegisterCostMinute(result.getWechatMinigameRegisterCostHour() - lastMinuteDWD.getWechatMinigameRegisterCostHour());
+                result.setWechatMinigameRegisterRateMinute(result.getWechatMinigameRegisterRateHour() - lastMinuteDWD.getWechatMinigameRegisterRateHour());
+                result.setWechatMinigameArpuMinute(result.getWechatMinigameArpuHour() - lastMinuteDWD.getWechatMinigameArpuHour());
+                result.setWechatMinigameRetentionCountMinute(result.getWechatMinigameRetentionCountHour() - lastMinuteDWD.getWechatMinigameRetentionCountHour());
+                result.setWechatMinigameCheckoutCountMinute(result.getWechatMinigameCheckoutCountHour() - lastMinuteDWD.getWechatMinigameCheckoutCountHour());
+                result.setWechatMinigameCheckoutAmountMinute(result.getWechatMinigameCheckoutAmountHour() - lastMinuteDWD.getWechatMinigameCheckoutAmountHour());
+                result.setOfficialAccountFollowCountMinute(result.getOfficialAccountFollowCountHour() - lastMinuteDWD.getOfficialAccountFollowCountHour());
+                result.setOfficialAccountFollowRateMinute(result.getOfficialAccountFollowRateHour() - lastMinuteDWD.getOfficialAccountFollowRateHour());
+                result.setOfficialAccountRegisterUserCountMinute(result.getOfficialAccountRegisterUserCountHour() - lastMinuteDWD.getOfficialAccountRegisterUserCountHour());
+                result.setOfficialAccountRegisterRateMinute(result.getOfficialAccountRegisterRateHour() - lastMinuteDWD.getOfficialAccountRegisterRateHour());
+                result.setOfficialAccountRegisterCostMinute(result.getOfficialAccountRegisterCostHour() - lastMinuteDWD.getOfficialAccountRegisterCostHour());
+                result.setOfficialAccountRegisterAmountMinute(result.getOfficialAccountRegisterAmountHour() - lastMinuteDWD.getOfficialAccountRegisterAmountHour());
+                result.setOfficialAccountRegisterRoiMinute(result.getOfficialAccountRegisterRoiHour() - lastMinuteDWD.getOfficialAccountRegisterRoiHour());
+                result.setOfficialAccountApplyCountMinute(result.getOfficialAccountApplyCountHour() - lastMinuteDWD.getOfficialAccountApplyCountHour());
+                result.setOfficialAccountApplyUserCountMinute(result.getOfficialAccountApplyUserCountHour() - lastMinuteDWD.getOfficialAccountApplyUserCountHour());
+                result.setOfficialAccountApplyRateMinute(result.getOfficialAccountApplyRateHour() - lastMinuteDWD.getOfficialAccountApplyRateHour());
+                result.setOfficialAccountApplyCostMinute(result.getOfficialAccountApplyCostHour() - lastMinuteDWD.getOfficialAccountApplyCostHour());
+                result.setOfficialAccountApplyAmountMinute(result.getOfficialAccountApplyAmountHour() - lastMinuteDWD.getOfficialAccountApplyAmountHour());
+                result.setOfficialAccountApplyRoiMinute(result.getOfficialAccountApplyRoiHour() - lastMinuteDWD.getOfficialAccountApplyRoiHour());
+                result.setOfficialAccountOrderCountMinute(result.getOfficialAccountOrderCountHour() - lastMinuteDWD.getOfficialAccountOrderCountHour());
+                result.setOfficialAccountFirstDayOrderCountMinute(result.getOfficialAccountFirstDayOrderCountHour() - lastMinuteDWD.getOfficialAccountFirstDayOrderCountHour());
+                result.setOfficialAccountOrderUserCountMinute(result.getOfficialAccountOrderUserCountHour() - lastMinuteDWD.getOfficialAccountOrderUserCountHour());
+                result.setOfficialAccountOrderRateMinute(result.getOfficialAccountOrderRateHour() - lastMinuteDWD.getOfficialAccountOrderRateHour());
+                result.setOfficialAccountOrderCostMinute(result.getOfficialAccountOrderCostHour() - lastMinuteDWD.getOfficialAccountOrderCostHour());
+                result.setOfficialAccountOrderAmountMinute(result.getOfficialAccountOrderAmountHour() - lastMinuteDWD.getOfficialAccountOrderAmountHour());
+                result.setOfficialAccountFirstDayOrderAmountMinute(result.getOfficialAccountFirstDayOrderAmountHour() - lastMinuteDWD.getOfficialAccountFirstDayOrderAmountHour());
+                result.setOfficialAccountOrderRoiMinute(result.getOfficialAccountOrderRoiHour() - lastMinuteDWD.getOfficialAccountOrderRoiHour());
+                result.setOfficialAccountConsultCountMinute(result.getOfficialAccountConsultCountHour() - lastMinuteDWD.getOfficialAccountConsultCountHour());
+                result.setOfficialAccountReaderCountMinute(result.getOfficialAccountReaderCountHour() - lastMinuteDWD.getOfficialAccountReaderCountHour());
+                result.setOfficialAccountCreditApplyUserCountMinute(result.getOfficialAccountCreditApplyUserCountHour() - lastMinuteDWD.getOfficialAccountCreditApplyUserCountHour());
+                result.setOfficialAccountCreditUserCountMinute(result.getOfficialAccountCreditUserCountHour() - lastMinuteDWD.getOfficialAccountCreditUserCountHour());
+                result.setForwardCountMinute(result.getForwardCountHour() - lastMinuteDWD.getForwardCountHour());
+                result.setForwardUserCountMinute(result.getForwardUserCountHour() - lastMinuteDWD.getForwardUserCountHour());
+                result.setNoInterestCountMinute(result.getNoInterestCountHour() - lastMinuteDWD.getNoInterestCountHour());
+            } else {
+                result.setCostDeviationRateMinute(statODS.getCostDeviationRate());
+                result.setCostMinute(statODS.getCost());
+                result.setCompensationAmountMinute(statODS.getCompensationAmount());
+                result.setViewCountMinute(statODS.getViewCount());
+                result.setThousandDisplayPriceMinute(statODS.getThousandDisplayPrice());
+                result.setAvgViewPerUserMinute(statODS.getAvgViewPerUser());
+                result.setValidClickCountMinute(statODS.getValidClickCount());
+                result.setCtrMinute(statODS.getCtr());
+                result.setCpcMinute(statODS.getCpc());
+                result.setValuableClickCountMinute(statODS.getValuableClickCount());
+                result.setValuableClickRateMinute(statODS.getValuableClickRate());
+                result.setValuableClickCostMinute(statODS.getValuableClickCost());
+                result.setConversionsCountMinute(statODS.getConversionsCount());
+                result.setConversionsCostMinute(statODS.getConversionsCost());
+                result.setConversionsRateMinute(statODS.getConversionsRate());
+                result.setDeepConversionsCountMinute(statODS.getDeepConversionsCount());
+                result.setDeepConversionsCostMinute(statODS.getDeepConversionsCost());
+                result.setDeepConversionsRateMinute(statODS.getDeepConversionsRate());
+                result.setOrderCountMinute(statODS.getOrderCount());
+                result.setFirstDayOrderCountMinute(statODS.getFirstDayOrderCount());
+                result.setWebOrderCostMinute(statODS.getWebOrderCost());
+                result.setOrderRateMinute(statODS.getOrderRate());
+                result.setOrderAmountMinute(statODS.getOrderAmount());
+                result.setFirstDayOrderAmountMinute(statODS.getFirstDayOrderAmount());
+                result.setOrderUnitPriceMinute(statODS.getOrderUnitPrice());
+                result.setOrderRoiMinute(statODS.getOrderRoi());
+                result.setSignInCountMinute(statODS.getSignInCount());
+                result.setScanFollowCountMinute(statODS.getScanFollowCount());
+                result.setWechatAppRegisterUvMinute(statODS.getWechatAppRegisterUv());
+                result.setWechatMinigameRegisterCostMinute(statODS.getWechatMinigameRegisterCost());
+                result.setWechatMinigameRegisterRateMinute(statODS.getWechatMinigameRegisterRate());
+                result.setWechatMinigameArpuMinute(statODS.getWechatMinigameArpu());
+                result.setWechatMinigameRetentionCountMinute(statODS.getWechatMinigameRetentionCount());
+                result.setWechatMinigameCheckoutCountMinute(statODS.getWechatMinigameCheckoutCount());
+                result.setWechatMinigameCheckoutAmountMinute(statODS.getWechatMinigameCheckoutAmount());
+                result.setOfficialAccountFollowCountMinute(statODS.getOfficialAccountFollowCount());
+                result.setOfficialAccountFollowRateMinute(statODS.getOfficialAccountFollowRate());
+                result.setOfficialAccountRegisterUserCountMinute(statODS.getOfficialAccountRegisterUserCount());
+                result.setOfficialAccountRegisterRateMinute(statODS.getOfficialAccountRegisterRate());
+                result.setOfficialAccountRegisterCostMinute(statODS.getOfficialAccountRegisterCost());
+                result.setOfficialAccountRegisterAmountMinute(statODS.getOfficialAccountRegisterAmount());
+                result.setOfficialAccountRegisterRoiMinute(statODS.getOfficialAccountRegisterRoi());
+                result.setOfficialAccountApplyCountMinute(statODS.getOfficialAccountApplyCount());
+                result.setOfficialAccountApplyUserCountMinute(statODS.getOfficialAccountApplyUserCount());
+                result.setOfficialAccountApplyRateMinute(statODS.getOfficialAccountApplyRate());
+                result.setOfficialAccountApplyCostMinute(statODS.getOfficialAccountApplyCost());
+                result.setOfficialAccountApplyAmountMinute(statODS.getOfficialAccountApplyAmount());
+                result.setOfficialAccountApplyRoiMinute(statODS.getOfficialAccountApplyRoi());
+                result.setOfficialAccountOrderCountMinute(statODS.getOfficialAccountOrderCount());
+                result.setOfficialAccountFirstDayOrderCountMinute(statODS.getOfficialAccountFirstDayOrderCount());
+                result.setOfficialAccountOrderUserCountMinute(statODS.getOfficialAccountOrderUserCount());
+                result.setOfficialAccountOrderRateMinute(statODS.getOfficialAccountOrderRate());
+                result.setOfficialAccountOrderCostMinute(statODS.getOfficialAccountOrderCost());
+                result.setOfficialAccountOrderAmountMinute(statODS.getOfficialAccountOrderAmount());
+                result.setOfficialAccountFirstDayOrderAmountMinute(statODS.getOfficialAccountFirstDayOrderAmount());
+                result.setOfficialAccountOrderRoiMinute(statODS.getOfficialAccountOrderRoi());
+                result.setOfficialAccountConsultCountMinute(statODS.getOfficialAccountConsultCount());
+                result.setOfficialAccountReaderCountMinute(statODS.getOfficialAccountReaderCount());
+                result.setOfficialAccountCreditApplyUserCountMinute(statODS.getOfficialAccountCreditApplyUserCount());
+                result.setOfficialAccountCreditUserCountMinute(statODS.getOfficialAccountCreditUserCount());
+                result.setForwardCountMinute(statODS.getForwardCount());
+                result.setForwardUserCountMinute(statODS.getForwardUserCount());
+                result.setNoInterestCountMinute(statODS.getNoInterestCount());
+            }
+        }
+        result.setAdGroupMapStr(PlanUtil.adGroupMapStr(adGroupMap));
+        result.setAdIdsStr(PlanUtil.adIdsStr(statODS.getAdId()));
+        return result;
+    }
+
+    private static PlanStatOfMinuteDWD initValue(PlanStatOfMinuteDWD dwd) {
+        dwd.setCostDeviationRateTotal(0.0);
+        dwd.setCostDeviationRateDay(0.0);
+        dwd.setCostDeviationRateHour(0.0);
+        dwd.setCostDeviationRateMinute(0.0);
+        dwd.setCostTotal(0L);
+        dwd.setCostDay(0L);
+        dwd.setCostHour(0L);
+        dwd.setCostMinute(0L);
+        dwd.setCompensationAmountTotal(0L);
+        dwd.setCompensationAmountDay(0L);
+        dwd.setCompensationAmountHour(0L);
+        dwd.setCompensationAmountMinute(0L);
+        dwd.setViewCountTotal(0L);
+        dwd.setViewCountDay(0L);
+        dwd.setViewCountHour(0L);
+        dwd.setViewCountMinute(0L);
+        dwd.setThousandDisplayPriceAll(0L);
+        dwd.setThousandDisplayPriceDay(0L);
+        dwd.setThousandDisplayPriceHour(0L);
+        dwd.setThousandDisplayPriceMinute(0L);
+        dwd.setAvgViewPerUserAll(0.0);
+        dwd.setAvgViewPerUserDay(0.0);
+        dwd.setAvgViewPerUserHour(0.0);
+        dwd.setAvgViewPerUserMinute(0.0);
+        dwd.setValidClickCountTotal(0L);
+        dwd.setValidClickCountDay(0L);
+        dwd.setValidClickCountHour(0L);
+        dwd.setValidClickCountMinute(0L);
+        dwd.setCtrAll(0.0);
+        dwd.setCtrDay(0.0);
+        dwd.setCtrHour(0.0);
+        dwd.setCtrMinute(0.0);
+        dwd.setCpcAll(0L);
+        dwd.setCpcDay(0L);
+        dwd.setCpcHour(0L);
+        dwd.setCpcMinute(0L);
+        dwd.setValuableClickCountTotal(0L);
+        dwd.setValuableClickCountDay(0L);
+        dwd.setValuableClickCountHour(0L);
+        dwd.setValuableClickCountMinute(0L);
+        dwd.setValuableClickRateAll(0.0);
+        dwd.setValuableClickRateDay(0.0);
+        dwd.setValuableClickRateHour(0.0);
+        dwd.setValuableClickRateMinute(0.0);
+        dwd.setValuableClickCostAll(0L);
+        dwd.setValuableClickCostDay(0L);
+        dwd.setValuableClickCostHour(0L);
+        dwd.setValuableClickCostMinute(0L);
+        dwd.setConversionsCountTotal(0L);
+        dwd.setConversionsCountDay(0L);
+        dwd.setConversionsCountHour(0L);
+        dwd.setConversionsCountMinute(0L);
+        dwd.setConversionsCostAll(0L);
+        dwd.setConversionsCostDay(0L);
+        dwd.setConversionsCostHour(0L);
+        dwd.setConversionsCostMinute(0L);
+        dwd.setConversionsRateAll(0.0);
+        dwd.setConversionsRateDay(0.0);
+        dwd.setConversionsRateHour(0.0);
+        dwd.setConversionsRateMinute(0.0);
+        dwd.setDeepConversionsCountTotal(0L);
+        dwd.setDeepConversionsCountDay(0L);
+        dwd.setDeepConversionsCountHour(0L);
+        dwd.setDeepConversionsCountMinute(0L);
+        dwd.setDeepConversionsCostAll(0L);
+        dwd.setDeepConversionsCostDay(0L);
+        dwd.setDeepConversionsCostHour(0L);
+        dwd.setDeepConversionsCostMinute(0L);
+        dwd.setDeepConversionsRateAll(0.0);
+        dwd.setDeepConversionsRateDay(0.0);
+        dwd.setDeepConversionsRateHour(0.0);
+        dwd.setDeepConversionsRateMinute(0.0);
+        dwd.setOrderCountTotal(0L);
+        dwd.setOrderCountDay(0L);
+        dwd.setOrderCountHour(0L);
+        dwd.setOrderCountMinute(0L);
+        dwd.setFirstDayOrderCountTotal(0L);
+        dwd.setFirstDayOrderCountDay(0L);
+        dwd.setFirstDayOrderCountHour(0L);
+        dwd.setFirstDayOrderCountMinute(0L);
+        dwd.setWebOrderCostAll(0L);
+        dwd.setWebOrderCostDay(0L);
+        dwd.setWebOrderCostHour(0L);
+        dwd.setWebOrderCostMinute(0L);
+        dwd.setOrderRateAll(0.0);
+        dwd.setOrderRateDay(0.0);
+        dwd.setOrderRateHour(0.0);
+        dwd.setOrderRateMinute(0.0);
+        dwd.setOrderAmountTotal(0L);
+        dwd.setOrderAmountDay(0L);
+        dwd.setOrderAmountHour(0L);
+        dwd.setOrderAmountMinute(0L);
+        dwd.setFirstDayOrderAmountTotal(0L);
+        dwd.setFirstDayOrderAmountDay(0L);
+        dwd.setFirstDayOrderAmountHour(0L);
+        dwd.setFirstDayOrderAmountMinute(0L);
+        dwd.setOrderUnitPriceAll(0L);
+        dwd.setOrderUnitPriceDay(0L);
+        dwd.setOrderUnitPriceHour(0L);
+        dwd.setOrderUnitPriceMinute(0L);
+        dwd.setOrderRoiAll(0.0);
+        dwd.setOrderRoiDay(0.0);
+        dwd.setOrderRoiHour(0.0);
+        dwd.setOrderRoiMinute(0.0);
+        dwd.setSignInCountTotal(0L);
+        dwd.setSignInCountDay(0L);
+        dwd.setSignInCountHour(0L);
+        dwd.setSignInCountMinute(0L);
+        dwd.setScanFollowCountTotal(0L);
+        dwd.setScanFollowCountDay(0L);
+        dwd.setScanFollowCountHour(0L);
+        dwd.setScanFollowCountMinute(0L);
+        dwd.setWechatAppRegisterUvTotal(0L);
+        dwd.setWechatAppRegisterUvDay(0L);
+        dwd.setWechatAppRegisterUvHour(0L);
+        dwd.setWechatAppRegisterUvMinute(0L);
+        dwd.setWechatMinigameRegisterCostAll(0L);
+        dwd.setWechatMinigameRegisterCostDay(0L);
+        dwd.setWechatMinigameRegisterCostHour(0L);
+        dwd.setWechatMinigameRegisterCostMinute(0L);
+        dwd.setWechatMinigameRegisterRateAll(0.0);
+        dwd.setWechatMinigameRegisterRateDay(0.0);
+        dwd.setWechatMinigameRegisterRateHour(0.0);
+        dwd.setWechatMinigameRegisterRateMinute(0.0);
+        dwd.setWechatMinigameArpuAll(0.0);
+        dwd.setWechatMinigameArpuDay(0.0);
+        dwd.setWechatMinigameArpuHour(0.0);
+        dwd.setWechatMinigameArpuMinute(0.0);
+        dwd.setWechatMinigameRetentionCountTotal(0L);
+        dwd.setWechatMinigameRetentionCountDay(0L);
+        dwd.setWechatMinigameRetentionCountHour(0L);
+        dwd.setWechatMinigameRetentionCountMinute(0L);
+        dwd.setWechatMinigameCheckoutCountTotal(0L);
+        dwd.setWechatMinigameCheckoutCountDay(0L);
+        dwd.setWechatMinigameCheckoutCountHour(0L);
+        dwd.setWechatMinigameCheckoutCountMinute(0L);
+        dwd.setWechatMinigameCheckoutAmountTotal(0L);
+        dwd.setWechatMinigameCheckoutAmountDay(0L);
+        dwd.setWechatMinigameCheckoutAmountHour(0L);
+        dwd.setWechatMinigameCheckoutAmountMinute(0L);
+        dwd.setOfficialAccountFollowCountTotal(0L);
+        dwd.setOfficialAccountFollowCountDay(0L);
+        dwd.setOfficialAccountFollowCountHour(0L);
+        dwd.setOfficialAccountFollowCountMinute(0L);
+        dwd.setOfficialAccountFollowRateAll(0.0);
+        dwd.setOfficialAccountFollowRateDay(0.0);
+        dwd.setOfficialAccountFollowRateHour(0.0);
+        dwd.setOfficialAccountFollowRateMinute(0.0);
+        dwd.setOfficialAccountRegisterUserCountTotal(0L);
+        dwd.setOfficialAccountRegisterUserCountDay(0L);
+        dwd.setOfficialAccountRegisterUserCountHour(0L);
+        dwd.setOfficialAccountRegisterUserCountMinute(0L);
+        dwd.setOfficialAccountRegisterRateAll(0.0);
+        dwd.setOfficialAccountRegisterRateDay(0.0);
+        dwd.setOfficialAccountRegisterRateHour(0.0);
+        dwd.setOfficialAccountRegisterRateMinute(0.0);
+        dwd.setOfficialAccountRegisterCostAll(0L);
+        dwd.setOfficialAccountRegisterCostDay(0L);
+        dwd.setOfficialAccountRegisterCostHour(0L);
+        dwd.setOfficialAccountRegisterCostMinute(0L);
+        dwd.setOfficialAccountRegisterAmountTotal(0L);
+        dwd.setOfficialAccountRegisterAmountDay(0L);
+        dwd.setOfficialAccountRegisterAmountHour(0L);
+        dwd.setOfficialAccountRegisterAmountMinute(0L);
+        dwd.setOfficialAccountRegisterRoiAll(0L);
+        dwd.setOfficialAccountRegisterRoiDay(0L);
+        dwd.setOfficialAccountRegisterRoiHour(0L);
+        dwd.setOfficialAccountRegisterRoiMinute(0L);
+        dwd.setOfficialAccountApplyCountTotal(0L);
+        dwd.setOfficialAccountApplyCountDay(0L);
+        dwd.setOfficialAccountApplyCountHour(0L);
+        dwd.setOfficialAccountApplyCountMinute(0L);
+        dwd.setOfficialAccountApplyUserCountTotal(0L);
+        dwd.setOfficialAccountApplyUserCountDay(0L);
+        dwd.setOfficialAccountApplyUserCountHour(0L);
+        dwd.setOfficialAccountApplyUserCountMinute(0L);
+        dwd.setOfficialAccountApplyRateAll(0.0);
+        dwd.setOfficialAccountApplyRateDay(0.0);
+        dwd.setOfficialAccountApplyRateHour(0.0);
+        dwd.setOfficialAccountApplyRateMinute(0.0);
+        dwd.setOfficialAccountApplyCostAll(0L);
+        dwd.setOfficialAccountApplyCostDay(0L);
+        dwd.setOfficialAccountApplyCostHour(0L);
+        dwd.setOfficialAccountApplyCostMinute(0L);
+        dwd.setOfficialAccountApplyAmountTotal(0L);
+        dwd.setOfficialAccountApplyAmountDay(0L);
+        dwd.setOfficialAccountApplyAmountHour(0L);
+        dwd.setOfficialAccountApplyAmountMinute(0L);
+        dwd.setOfficialAccountApplyRoiAll(0L);
+        dwd.setOfficialAccountApplyRoiDay(0L);
+        dwd.setOfficialAccountApplyRoiHour(0L);
+        dwd.setOfficialAccountApplyRoiMinute(0L);
+        dwd.setOfficialAccountOrderCountTotal(0L);
+        dwd.setOfficialAccountOrderCountDay(0L);
+        dwd.setOfficialAccountOrderCountHour(0L);
+        dwd.setOfficialAccountOrderCountMinute(0L);
+        dwd.setOfficialAccountFirstDayOrderCountTotal(0L);
+        dwd.setOfficialAccountFirstDayOrderCountDay(0L);
+        dwd.setOfficialAccountFirstDayOrderCountHour(0L);
+        dwd.setOfficialAccountFirstDayOrderCountMinute(0L);
+        dwd.setOfficialAccountOrderUserCountTotal(0L);
+        dwd.setOfficialAccountOrderUserCountDay(0L);
+        dwd.setOfficialAccountOrderUserCountHour(0L);
+        dwd.setOfficialAccountOrderUserCountMinute(0L);
+        dwd.setOfficialAccountOrderRateAll(0.0);
+        dwd.setOfficialAccountOrderRateDay(0.0);
+        dwd.setOfficialAccountOrderRateHour(0.0);
+        dwd.setOfficialAccountOrderRateMinute(0.0);
+        dwd.setOfficialAccountOrderCostAll(0L);
+        dwd.setOfficialAccountOrderCostDay(0L);
+        dwd.setOfficialAccountOrderCostHour(0L);
+        dwd.setOfficialAccountOrderCostMinute(0L);
+        dwd.setOfficialAccountOrderAmountTotal(0L);
+        dwd.setOfficialAccountOrderAmountDay(0L);
+        dwd.setOfficialAccountOrderAmountHour(0L);
+        dwd.setOfficialAccountOrderAmountMinute(0L);
+        dwd.setOfficialAccountFirstDayOrderAmountTotal(0L);
+        dwd.setOfficialAccountFirstDayOrderAmountDay(0L);
+        dwd.setOfficialAccountFirstDayOrderAmountHour(0L);
+        dwd.setOfficialAccountFirstDayOrderAmountMinute(0L);
+        dwd.setOfficialAccountOrderRoiAll(0L);
+        dwd.setOfficialAccountOrderRoiDay(0L);
+        dwd.setOfficialAccountOrderRoiHour(0L);
+        dwd.setOfficialAccountOrderRoiMinute(0L);
+        dwd.setOfficialAccountConsultCountTotal(0L);
+        dwd.setOfficialAccountConsultCountDay(0L);
+        dwd.setOfficialAccountConsultCountHour(0L);
+        dwd.setOfficialAccountConsultCountMinute(0L);
+        dwd.setOfficialAccountReaderCountTotal(0L);
+        dwd.setOfficialAccountReaderCountDay(0L);
+        dwd.setOfficialAccountReaderCountHour(0L);
+        dwd.setOfficialAccountReaderCountMinute(0L);
+        dwd.setOfficialAccountCreditApplyUserCountTotal(0L);
+        dwd.setOfficialAccountCreditApplyUserCountDay(0L);
+        dwd.setOfficialAccountCreditApplyUserCountHour(0L);
+        dwd.setOfficialAccountCreditApplyUserCountMinute(0L);
+        dwd.setOfficialAccountCreditUserCountTotal(0L);
+        dwd.setOfficialAccountCreditUserCountDay(0L);
+        dwd.setOfficialAccountCreditUserCountHour(0L);
+        dwd.setOfficialAccountCreditUserCountMinute(0L);
+        dwd.setForwardCountTotal(0L);
+        dwd.setForwardCountDay(0L);
+        dwd.setForwardCountHour(0L);
+        dwd.setForwardCountMinute(0L);
+        dwd.setForwardUserCountTotal(0L);
+        dwd.setForwardUserCountDay(0L);
+        dwd.setForwardUserCountHour(0L);
+        dwd.setForwardUserCountMinute(0L);
+        dwd.setNoInterestCountTotal(0L);
+        dwd.setNoInterestCountDay(0L);
+        dwd.setNoInterestCountHour(0L);
+        dwd.setNoInterestCountMinute(0L);
+        return dwd;
+    }
+}

+ 0 - 10
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/pojo/properties/KafkaProperties.java

@@ -1,10 +0,0 @@
-package flink.zanxiangnet.ad.monitoring.pojo.properties;
-
-public class KafkaProperties {
-    public static final String KAFKA_SERVERS = "kafka.servers";
-    public static final String KAFKA_USERNAME = "kafka.username";
-    public static final String KAFKA_PASSWORD = "kafka.password";
-    public static final String KAFKA_SSL_PATH = "kafka.sslPath";
-    public static final String KAFKA_TOPIC = "kafka.topic";
-    public static final String KAFKA_GROUP_ID = "kafka.groupId";
-}

+ 23 - 12
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/process/AdMinuteDWDProcess.java

@@ -24,12 +24,14 @@ import org.apache.flink.util.Collector;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
 public class AdMinuteDWDProcess extends ProcessWindowFunction<AdDataOfMinuteODS, AdStatOfMinuteDWD, Long, TimeWindow> {
+    private static final DateTimeFormatter formatForLastReduceKey = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm");
     private Odps odps;
 
     // 历史的天数据
@@ -39,7 +41,7 @@ public class AdMinuteDWDProcess extends ProcessWindowFunction<AdDataOfMinuteODS,
     // 之前聚合的昨天的数据
     private MapState<String, AdStatOfMinuteDWD> yesterdayMinuteState;
     // 前 5分钟聚合的数据
-    private ValueState<AdStatOfMinuteDWD> lastReduceState;
+    private MapState<String, AdStatOfMinuteDWD> lastReduceState;
 
     @Override
     public void open(Configuration conf) {
@@ -57,7 +59,7 @@ public class AdMinuteDWDProcess extends ProcessWindowFunction<AdDataOfMinuteODS,
         historyDayState = getRuntimeContext().getState(new ValueStateDescriptor<>("historyDayState", AdStatOfDayDWD.class));
         lastQueryDayState = getRuntimeContext().getState(new ValueStateDescriptor<>("lastQueryDayState", String.class));
         yesterdayMinuteState = getRuntimeContext().getMapState(new MapStateDescriptor<>("yesterdayMinuteState", String.class, AdStatOfMinuteDWD.class));
-        lastReduceState = getRuntimeContext().getState(new ValueStateDescriptor<>("lastReduceState", AdStatOfMinuteDWD.class));
+        lastReduceState = getRuntimeContext().getMapState(new MapStateDescriptor<>("lastReduceState", String.class, AdStatOfMinuteDWD.class));
     }
 
     @Override
@@ -109,21 +111,30 @@ public class AdMinuteDWDProcess extends ProcessWindowFunction<AdDataOfMinuteODS,
         }
         AdStatOfDayDWD beforeYesterdayDayDWD = historyDayState.value();
 
-        AdStatOfMinuteDWD lastReduceData = lastReduceState.value();
-        if (lastReduceData != null) {
-            if (lastReduceData.getStatTime() != (beginTime - 5 * 60 * 1000L)) {
-                lastReduceData = null;
-            }
-        }
+        AdStatOfMinuteDWD lastReduceData = lastReduceState.get(beginDateTime.minusMinutes(5L).format(formatForLastReduceKey));
         // 聚合当天的全部数据
         AdStatOfMinuteDWD newAdStat = AdStatOfMinuteDWD.reduce(beforeYesterdayDayDWD, yesterdayMinuteDWD, adDataOfMinuteODSList, statODS, lastReduceData, now);
         collector.collect(newAdStat);
         System.out.println("输出:" + JsonUtil.toString(newAdStat));
-        lastReduceState.update(newAdStat);
+        lastReduceState.put(beginDateTime.format(formatForLastReduceKey), newAdStat);
         yesterdayMinuteState.put(statDay, newAdStat);
-        yesterdayMinuteState.remove(DateUtil.formatLocalDate(beginDate.minusDays(2L)));
-        yesterdayMinuteState.remove(DateUtil.formatLocalDate(beginDate.minusDays(3L)));
-        yesterdayMinuteState.remove(DateUtil.formatLocalDate(beginDate.minusDays(4L)));
+        
+        for (String key : lastReduceState.keys()) {
+            if (key.equals(beginDateTime.format(formatForLastReduceKey))
+                    || key.equals(beginDateTime.minusMinutes(5L).format(formatForLastReduceKey))
+                    || key.equals(beginDateTime.minusMinutes(10L).format(formatForLastReduceKey))) {
+                continue;
+            }
+            lastReduceState.remove(key);
+        }
+        for (String key : yesterdayMinuteState.keys()) {
+            if (key.equals(DateUtil.formatLocalDate(beginDate))
+                    || key.equals(DateUtil.formatLocalDate(beginDate.minusDays(1L)))
+                    || key.equals(DateUtil.formatLocalDate(beginDate.minusDays(2L)))) {
+                continue;
+            }
+            yesterdayMinuteState.remove(key);
+        }
     }
 
     @Override

+ 139 - 0
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/process/PlanMinuteDWDProcess.java

@@ -0,0 +1,139 @@
+package flink.zanxiangnet.ad.monitoring.process;
+
+import com.aliyun.odps.Instance;
+import com.aliyun.odps.Odps;
+import com.aliyun.odps.account.Account;
+import com.aliyun.odps.account.AliyunAccount;
+import com.aliyun.odps.data.Record;
+import com.aliyun.odps.task.SQLTask;
+import flink.zanxiangnet.ad.monitoring.maxcompute.MaxComputeLog;
+import flink.zanxiangnet.ad.monitoring.pojo.entity.*;
+import flink.zanxiangnet.ad.monitoring.pojo.properties.ApplicationProperties;
+import flink.zanxiangnet.ad.monitoring.util.DateUtil;
+import org.apache.flink.api.common.state.MapState;
+import org.apache.flink.api.common.state.MapStateDescriptor;
+import org.apache.flink.api.common.state.ValueState;
+import org.apache.flink.api.common.state.ValueStateDescriptor;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
+import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
+import org.apache.flink.util.Collector;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class PlanMinuteDWDProcess extends ProcessWindowFunction<AdDataOfMinuteODS, PlanStatOfMinuteDWD, Long, TimeWindow> {
+    private static final DateTimeFormatter formatForLastReduceKey = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm");
+    private Odps odps;
+
+    // 历史的天数据
+    private ValueState<PlanStatOfDayDWD> historyDayState;
+    // 上次查询的天数据
+    private ValueState<String> lastQueryDayState;
+    // 之前聚合的昨天的数据
+    private MapState<String, PlanStatOfMinuteDWD> yesterdayMinuteState;
+    // 前 5分钟聚合的数据
+    private MapState<String, PlanStatOfMinuteDWD> lastReduceState;
+
+    @Override
+    public void open(Configuration conf) {
+        Map<String, String> params = getRuntimeContext()
+                .getExecutionConfig()
+                .getGlobalJobParameters()
+                .toMap();
+        Account account = new AliyunAccount(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_ID),
+                params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_KEY));
+        odps = new Odps(account);
+        odps.getRestClient().setRetryLogger(new MaxComputeLog());
+        odps.setEndpoint(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_ENDPOINT));
+        odps.setDefaultProject(params.get(ApplicationProperties.MAX_COMPUTE_ACCOUNT_PROJECT_NAME));
+
+        historyDayState = getRuntimeContext().getState(new ValueStateDescriptor<>("historyDayState", PlanStatOfDayDWD.class));
+        lastQueryDayState = getRuntimeContext().getState(new ValueStateDescriptor<>("lastQueryDayState", String.class));
+        yesterdayMinuteState = getRuntimeContext().getMapState(new MapStateDescriptor<>("yesterdayMinuteState", String.class, PlanStatOfMinuteDWD.class));
+        lastReduceState = getRuntimeContext().getMapState(new MapStateDescriptor<>("lastReduceState", String.class, PlanStatOfMinuteDWD.class));
+    }
+
+    @Override
+    public void process(Long elementCount, ProcessWindowFunction<AdDataOfMinuteODS, PlanStatOfMinuteDWD, Long, TimeWindow>.Context context,
+                        Iterable<AdDataOfMinuteODS> iterable, Collector<PlanStatOfMinuteDWD> collector) throws Exception {
+        long beginTime = context.window().getStart();
+        LocalDateTime beginDateTime = DateUtil.milliToLocalDateTime(beginTime);
+        LocalDate beginDate = beginDateTime.toLocalDate();
+        String statDay = DateUtil.formatLocalDate(beginDate);
+        int hour = beginDateTime.getHour();
+        long now = System.currentTimeMillis();
+        AdDataOfMinuteODS statODS = null;
+        List<AdDataOfMinuteODS> adDataOfMinuteODSList = new ArrayList<>(24);
+        for (AdDataOfMinuteODS adDataOfMinuteODS : iterable) {
+            adDataOfMinuteODSList.add(adDataOfMinuteODS);
+            if (adDataOfMinuteODS.getHour() != hour) {
+                continue;
+            }
+            statODS = adDataOfMinuteODS;
+            break;
+        }
+        if (statODS == null) {
+            return;
+        }
+
+        Long campaignId = statODS.getCampaignId();
+
+        // 昨天聚合的数据
+        PlanStatOfMinuteDWD yesterdayMinuteDWD = yesterdayMinuteState.get(DateUtil.formatLocalDate(beginDate.minusDays(1L)));
+        // 之前的数据
+        String lastQueryDay = lastQueryDayState.value();
+        if (lastQueryDay == null || !lastQueryDay.equals(statDay)) {
+            // 往前找 30天
+            LocalDate endDay = beginDate.minusDays(2L), beginDay = endDay.minusDays(30L);
+            Instance instance = SQLTask.run(odps, "SELECT * FROM plan_stat_of_day_dwd WHERE stat_day >= \"" + DateUtil.formatLocalDate(beginDay) + "\" AND stat_day <= \"" + endDay + "\" AND campaign_id = " + campaignId + ";");
+            instance.waitForSuccess();
+            List<Record> records = SQLTask.getResult(instance);
+            List<PlanStatOfDayDWD> historyDayData = records.stream().map(PlanStatOfDayDWD::byMaxCompute).sorted((val1, val2) -> {
+                if (val1.getStatDay().equals(val2.getStatDay())) {
+                    return new Long(val1.getCreateTime().getTime() - val2.getCreateTime().getTime()).intValue();
+                }
+                return DateUtil.parseLocalDate(val1.getStatDay()).compareTo(DateUtil.parseLocalDate(val2.getStatDay()));
+            }).collect(Collectors.toList());
+            if (!historyDayData.isEmpty()) {
+                historyDayState.update(historyDayData.get(historyDayData.size() - 1));
+            }
+            lastQueryDayState.update(statDay);
+        }
+        PlanStatOfDayDWD beforeYesterdayDayDWD = historyDayState.value();
+
+        PlanStatOfMinuteDWD lastReduceData = lastReduceState.get(beginDateTime.minusMinutes(5L).format(formatForLastReduceKey));
+        // 聚合当天的全部数据
+        PlanStatOfMinuteDWD newAdStat = PlanStatOfMinuteDWD.reduce(beforeYesterdayDayDWD, yesterdayMinuteDWD, adDataOfMinuteODSList, statODS, lastReduceData, now);
+        collector.collect(newAdStat);
+        lastReduceState.put(beginDateTime.format(formatForLastReduceKey), newAdStat);
+        yesterdayMinuteState.put(statDay, newAdStat);
+
+        for (String key : lastReduceState.keys()) {
+            if (key.equals(beginDateTime.format(formatForLastReduceKey))
+                    || key.equals(beginDateTime.minusMinutes(5L).format(formatForLastReduceKey))
+                    || key.equals(beginDateTime.minusMinutes(10L).format(formatForLastReduceKey))) {
+                continue;
+            }
+            lastReduceState.remove(key);
+        }
+        for (String key : yesterdayMinuteState.keys()) {
+            if (key.equals(DateUtil.formatLocalDate(beginDate))
+                    || key.equals(DateUtil.formatLocalDate(beginDate.minusDays(1L)))
+                    || key.equals(DateUtil.formatLocalDate(beginDate.minusDays(2L)))) {
+                continue;
+            }
+            yesterdayMinuteState.remove(key);
+        }
+    }
+
+    @Override
+    public void clear(ProcessWindowFunction<AdDataOfMinuteODS, PlanStatOfMinuteDWD, Long, TimeWindow>.Context context) throws Exception {
+        System.out.println("窗口关闭");
+    }
+}

+ 38 - 0
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/trigger/AdMinuteODSStreamTrigger.java

@@ -0,0 +1,38 @@
+package flink.zanxiangnet.ad.monitoring.trigger;
+
+import flink.zanxiangnet.ad.monitoring.pojo.entity.AdDataOfMinuteODS;
+import flink.zanxiangnet.ad.monitoring.util.DateUtil;
+import org.apache.flink.streaming.api.windowing.triggers.Trigger;
+import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
+import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
+
+public class AdMinuteODSStreamTrigger extends Trigger<AdDataOfMinuteODS, TimeWindow> {
+    @Override
+    public TriggerResult onElement(AdDataOfMinuteODS adDataOfMinuteODS, long time, TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
+        // FIXME 水印时间没有意义!拿到数据是 Long.MAX
+        if (timeWindow.maxTimestamp() <= triggerContext.getCurrentWatermark()) {
+            // 到了窗口的最大生命周期
+            return TriggerResult.FIRE_AND_PURGE;
+        }
+        triggerContext.registerEventTimeTimer(timeWindow.maxTimestamp());
+        if (adDataOfMinuteODS.getHour() == DateUtil.milliToLocalDateTime(time).getHour()) {
+            return TriggerResult.FIRE;
+        }
+        return TriggerResult.CONTINUE;
+    }
+
+    @Override
+    public TriggerResult onProcessingTime(long time, TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
+        return TriggerResult.CONTINUE;
+    }
+
+    @Override
+    public TriggerResult onEventTime(long time, TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
+        return time == timeWindow.maxTimestamp() ? TriggerResult.FIRE : TriggerResult.CONTINUE;
+    }
+
+    @Override
+    public void clear(TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
+        triggerContext.deleteEventTimeTimer(timeWindow.maxTimestamp());
+    }
+}

+ 38 - 0
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/trigger/PlanMinuteODSStreamTrigger.java

@@ -0,0 +1,38 @@
+package flink.zanxiangnet.ad.monitoring.trigger;
+
+import flink.zanxiangnet.ad.monitoring.pojo.entity.AdDataOfMinuteODS;
+import flink.zanxiangnet.ad.monitoring.util.DateUtil;
+import org.apache.flink.streaming.api.windowing.triggers.Trigger;
+import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
+import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
+
+public class PlanMinuteODSStreamTrigger extends Trigger<AdDataOfMinuteODS, TimeWindow> {
+    @Override
+    public TriggerResult onElement(AdDataOfMinuteODS adDataOfMinuteODS, long time, TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
+        // FIXME 水印时间没有意义!拿到数据是 Long.MAX
+        if (timeWindow.maxTimestamp() <= triggerContext.getCurrentWatermark()) {
+            // 到了窗口的最大生命周期
+            return TriggerResult.FIRE_AND_PURGE;
+        }
+        triggerContext.registerEventTimeTimer(timeWindow.maxTimestamp());
+        if (adDataOfMinuteODS.getHour() == DateUtil.milliToLocalDateTime(time).getHour()) {
+            return TriggerResult.FIRE;
+        }
+        return TriggerResult.CONTINUE;
+    }
+
+    @Override
+    public TriggerResult onProcessingTime(long time, TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
+        return TriggerResult.CONTINUE;
+    }
+
+    @Override
+    public TriggerResult onEventTime(long time, TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
+        return time == timeWindow.maxTimestamp() ? TriggerResult.FIRE : TriggerResult.CONTINUE;
+    }
+
+    @Override
+    public void clear(TimeWindow timeWindow, TriggerContext triggerContext) throws Exception {
+        triggerContext.deleteEventTimeTimer(timeWindow.maxTimestamp());
+    }
+}

+ 18 - 5
flink-ad-monitoring/src/main/java/flink/zanxiangnet/ad/monitoring/util/PlanUtil.java

@@ -14,14 +14,14 @@ public class PlanUtil {
         return Arrays.stream(adIdsStr.split(",")).map(Long::valueOf).collect(Collectors.toSet());
     }
 
-    public static Map<Long, List<Long>> parseAdGroupMapStr(String adGroupMapStr) {
+    public static Map<Long, Set<Long>> parseAdGroupMapStr(String adGroupMapStr) {
         if (StringUtils.isBlank(adGroupMapStr)) {
             return new HashMap<>(2);
         }
-        Map<Long, List> mapTemp = JsonUtil.toMap(adGroupMapStr, Map.class, Long.class, List.class);
-        Map<Long, List<Long>> adGroupMap = new HashMap<>(mapTemp.size());
+        Map<Long, Set> mapTemp = JsonUtil.toMap(adGroupMapStr, Map.class, Long.class, Set.class);
+        Map<Long, Set<Long>> adGroupMap = new HashMap<>(mapTemp.size());
         mapTemp.forEach((key, value) -> {
-            List<Long> temp = new ArrayList<>(value.size());
+            Set<Long> temp = new HashSet<>(value.size());
             value.forEach(adId -> temp.add(ObjectUtil.toLong(adId)));
             adGroupMap.put(key, temp);
         });
@@ -39,10 +39,23 @@ public class PlanUtil {
         return String.valueOf(adId);
     }
 
-    public static String adGroupMapStr(Map<Long, List<Long>> adGroupIds) {
+    public static String adGroupMapStr(Map<Long, Set<Long>> adGroupIds) {
         if (adGroupIds == null || adGroupIds.isEmpty()) {
             return null;
         }
         return JsonUtil.toString(adGroupIds);
     }
+
+    public static void reduceAdGroup(Map<Long, Set<Long>> value1, Map<Long, Set<Long>> value2) {
+        value1.forEach((key, value) -> {
+            if (value2.containsKey(key)) {
+                value.addAll(value2.get(key));
+            }
+        });
+        value2.forEach((key, value) -> {
+            if (!value1.containsKey(key)) {
+                value1.put(key, value);
+            }
+        });
+    }
 }

+ 0 - 13
flink-ad-monitoring/src/main/resources/ad_stream_of_day.properties

@@ -1,13 +0,0 @@
-kafka.servers=114.55.59.94:9093,112.124.33.132:9093
-kafka.username=alikafka_pre-cn-tl32fsx4l00x
-kafka.password=VOEdhZLjOrL76lrl5bqPtydtoEkbs0Ny
-kafka.sslPath=D:\\Downloads\\kafka.client.truststore.jks
-# kafka.sslPath=/root/flink-1.13.2/kafka.client.truststore.jks
-kafka.topic=ad_day_cost_topic
-kafka.groupId=ad_day_cost_group
-
-#kafka.servers=172.17.174.73:9092,172.17.174.72:9092
-#kafka.username=
-#kafka.password=
-#kafka.topic=ad_day_cost_topic
-#kafka.groupId=ad_day_consumer

+ 0 - 13
flink-ad-monitoring/src/main/resources/ad_stream_of_minute.properties

@@ -1,13 +0,0 @@
-kafka.servers=114.55.59.94:9093,112.124.33.132:9093
-kafka.username=alikafka_pre-cn-tl32fsx4l00x
-kafka.password=VOEdhZLjOrL76lrl5bqPtydtoEkbs0Ny
-kafka.sslPath=D:\\Downloads\\kafka.client.truststore.jks
-#kafka.sslPath=/root/flink-1.13.2/kafka.client.truststore.jks
-kafka.topic=ad_cost_topic
-kafka.groupId=ad_cost_group
-
-#kafka.servers=172.17.174.73:9092,172.17.174.72:9092
-#kafka.username=
-#kafka.password=
-#kafka.topic=ad_cost_topic
-#kafka.groupId=ad_minute_consumer

+ 6 - 1
flink-ad-monitoring/src/main/resources/application.properties

@@ -7,4 +7,9 @@ maxCompute.tunnelEndpoint=http://dt.cn-hangzhou.maxcompute.aliyun.com
 #maxCompute.accountKey=rjOgeffIRMV7r5T6LBsOC47GI60Xge
 #maxCompute.endpoint=http://service.cn-hangzhou.maxcompute.aliyun.com/api
 #maxCompute.projectName=zx_test02
-#maxCompute.tunnelEndpoint=http://dt.cn-hangzhou.maxcompute.aliyun.com
+#maxCompute.tunnelEndpoint=http://dt.cn-hangzhou.maxcompute.aliyun.com
+kafka.servers=114.55.59.94:9093,112.124.33.132:9093
+kafka.username=alikafka_pre-cn-tl32fsx4l00x
+kafka.password=VOEdhZLjOrL76lrl5bqPtydtoEkbs0Ny
+kafka.sslPath=D:\\Downloads\\kafka.client.truststore.jks
+kafka.connModule=SASL_SSL