wfansh пре 4 месеци
родитељ
комит
2704c2b16b

+ 15 - 0
src/main/java/com/wechi/adweb/bridge/exception/ExceptionAdvice.java

@@ -1,5 +1,6 @@
 package com.wechi.adweb.bridge.exception;
 
+import com.google.ads.googleads.v18.errors.GoogleAdsException;
 import com.wechi.adweb.bridge.common.APIStatus;
 import com.wechi.adweb.bridge.common.OpenAPIResponse;
 
@@ -67,6 +68,20 @@ public class ExceptionAdvice {
      */
     @ResponseBody
     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    @ExceptionHandler(GoogleAdsException.class)
+    public OpenAPIResponse<?> handleGoogleAdsException(GoogleAdsException e) {
+        log.error(e.getMessage(), e);
+        return OpenAPIResponse.builder()
+                .status(APIStatus.INTERNAL_SERVER_ERROR)
+                .message(e.getMessage())
+                .build();
+    }
+
+    /**
+     * @return 500 INTERNAL_SERVER_ERROR
+     */
+    @ResponseBody
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
     @ExceptionHandler(Exception.class)
     public OpenAPIResponse<?> handleException(Exception e) {
         log.error(e.getMessage(), e);

+ 33 - 0
src/main/java/com/wechi/adweb/bridge/google/ads/controller/GoogleAdsController.java

@@ -7,6 +7,8 @@ import com.wechi.adweb.bridge.common.OpenAPIResponse;
 import com.wechi.adweb.bridge.exception.BadRequestException;
 import com.wechi.adweb.bridge.exception.DataException;
 import com.wechi.adweb.bridge.google.ads.dto.CustomerStatsDTO;
+import com.wechi.adweb.bridge.google.ads.dto.MetricsDTO;
+import com.wechi.adweb.bridge.google.ads.dto.ReportRequestDTO;
 import com.wechi.adweb.bridge.google.ads.service.GoogleAdsService;
 import com.wechi.adweb.bridge.util.JsonUtils;
 
@@ -18,6 +20,8 @@ import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.Map;
+
 /**
  * @author wfansh
  */
@@ -52,4 +56,33 @@ public class GoogleAdsController extends BaseController {
                 .data(customerStats)
                 .build();
     }
+
+    @PostMapping("/dailyStats")
+    @ResponseBody
+    public OpenAPIResponse<Map<String, MetricsDTO>> getDailyStats(
+            @RequestBody OpenAPIRequest<ReportRequestDTO> apiRequest)
+            throws BadRequestException, DataException {
+        long start = System.currentTimeMillis();
+        log.info("****** getDailyStats() ****** apiRequest = {}", JsonUtils.toJson(apiRequest));
+        ReportRequestDTO reportRequest = apiRequest.getData();
+
+        // 1. Validates the request parameters.
+        if (!validateReportRequest(reportRequest)) {
+            throw new BadRequestException(apiRequest);
+        }
+
+        // 2. Executes the API request.
+        Map<String, MetricsDTO> dailyStats = googleAdsService.getDailyStats(reportRequest);
+        log.info("****** getDailyStats() ****** duration = {} seconds", getElapsedSeconds(start));
+        return OpenAPIResponse.<Map<String, MetricsDTO>>builder()
+                .status(APIStatus.SUCCESS)
+                .data(dailyStats)
+                .build();
+    }
+
+    private boolean validateReportRequest(ReportRequestDTO reportRequest) {
+        return StringUtils.isNotEmpty(reportRequest.getCustomerId())
+                && StringUtils.isNotEmpty(reportRequest.getStartDate())
+                && StringUtils.isNotEmpty(reportRequest.getEndDate());
+    }
 }

+ 3 - 5
src/main/java/com/wechi/adweb/bridge/google/ads/dto/MetricsDTO.java

@@ -2,15 +2,13 @@ package com.wechi.adweb.bridge.google.ads.dto;
 
 import com.google.ads.googleads.v18.common.Metrics;
 
+import lombok.Builder;
 import lombok.Data;
-import lombok.experimental.SuperBuilder;
 
-/**
- * @author wfansh
- */
 @Data
-@SuperBuilder
+@Builder
 public class MetricsDTO {
+
     private long impressions;
     private long clicks;
 

+ 38 - 0
src/main/java/com/wechi/adweb/bridge/google/ads/dto/ReportRequestDTO.java

@@ -0,0 +1,38 @@
+package com.wechi.adweb.bridge.google.ads.dto;
+
+import com.wechi.adweb.bridge.exception.DataException;
+
+import lombok.Data;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @author wfansh
+ */
+@Data
+public class ReportRequestDTO {
+
+    private String customerId;
+
+    private String startDate;
+
+    private String endDate;
+
+    private int limit;
+
+    /** Ensures the limit value is positive by defaulting to 1000. */
+    public int getLimit() {
+        return limit > 0 ? limit : 1000;
+    }
+
+    public String toDateClause() throws DataException {
+        if (StringUtils.isEmpty(startDate) || StringUtils.isEmpty(endDate)) {
+            throw new DataException(
+                    String.format(
+                            "Invalid date range, startDate = %s, endDate = %s",
+                            startDate, endDate));
+        }
+
+        return String.format("segments.date BETWEEN '%s' AND '%s'", startDate, endDate);
+    }
+}

+ 39 - 1
src/main/java/com/wechi/adweb/bridge/google/ads/service/GoogleAdsService.java

@@ -8,6 +8,8 @@ import com.google.ads.googleads.v18.services.SearchGoogleAdsRequest;
 import com.google.api.core.ApiFuture;
 import com.wechi.adweb.bridge.exception.DataException;
 import com.wechi.adweb.bridge.google.ads.dto.CustomerStatsDTO;
+import com.wechi.adweb.bridge.google.ads.dto.MetricsDTO;
+import com.wechi.adweb.bridge.google.ads.dto.ReportRequestDTO;
 
 import lombok.extern.slf4j.Slf4j;
 
@@ -15,7 +17,10 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDate;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
 /**
@@ -24,7 +29,6 @@ import java.util.stream.StreamSupport;
 @Slf4j
 @Service
 public class GoogleAdsService {
-
     @Autowired private GoogleAdsClient googleAdsClient;
 
     public CustomerStatsDTO getCustomerStats(String customerId) throws DataException {
@@ -87,4 +91,38 @@ public class GoogleAdsService {
             throw new DataException(e);
         }
     }
+
+    public Map<String, MetricsDTO> getDailyStats(ReportRequestDTO reportRequest)
+            throws DataException {
+        try (GoogleAdsServiceClient googleAdsServiceClient =
+                googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
+            String query =
+                    String.format(
+                            "SELECT customer.descriptive_name, customer.currency_code, "
+                                    + "segments.date, "
+                                    + "metrics.impressions, metrics.clicks, metrics.ctr, metrics.average_cpc, "
+                                    + "metrics.average_cpm, metrics.conversions, metrics.cost_micros "
+                                    + "FROM customer WHERE %s "
+                                    + "ORDER BY segments.date LIMIT %d",
+                            reportRequest.toDateClause(), reportRequest.getLimit());
+
+            SearchGoogleAdsRequest request =
+                    SearchGoogleAdsRequest.newBuilder()
+                            .setCustomerId(reportRequest.getCustomerId())
+                            .setQuery(query)
+                            .build();
+
+            SearchPagedResponse response = googleAdsServiceClient.search(request);
+            return StreamSupport.stream(response.iterateAll().spliterator(), false)
+                    .collect(
+                            Collectors.toMap(
+                                    googleAdsRow -> googleAdsRow.getSegments().getDate(),
+                                    googleAdsRow ->
+                                            MetricsDTO.fromMetrics(googleAdsRow.getMetrics()),
+                                    // For duplicated dates.
+                                    MetricsDTO::merge,
+                                    // Preserves the original order in the response.
+                                    LinkedHashMap::new));
+        }
+    }
 }

+ 12 - 0
src/test/java/com/wechi/adweb/bridge/google/ads/GoogleAdsServiceTest.java

@@ -2,6 +2,7 @@ package com.wechi.adweb.bridge.google.ads;
 
 import com.wechi.adweb.bridge.exception.DataException;
 import com.wechi.adweb.bridge.google.ads.dto.CustomerStatsDTO;
+import com.wechi.adweb.bridge.google.ads.dto.ReportRequestDTO;
 import com.wechi.adweb.bridge.google.ads.service.GoogleAdsService;
 import com.wechi.adweb.bridge.util.JsonUtils;
 
@@ -24,4 +25,15 @@ public class GoogleAdsServiceTest {
         CustomerStatsDTO customerStats = googleAdsService.getCustomerStats(customerId);
         System.out.println(JsonUtils.toJson(customerStats));
     }
+
+    @Test
+    void test() throws DataException {
+        ReportRequestDTO reportRequest = new ReportRequestDTO();
+        reportRequest.setCustomerId(customerId);
+        reportRequest.setStartDate("2024-12-01");
+        reportRequest.setEndDate("2024-12-06");
+        reportRequest.setLimit(100);
+
+        System.out.println(JsonUtils.toJson(googleAdsService.getDailyStats(reportRequest)));
+    }
 }