diff --git a/app/controller/GoogleAdsController.php b/app/controller/GoogleAdsController.php index 4f72286..3536e81 100644 --- a/app/controller/GoogleAdsController.php +++ b/app/controller/GoogleAdsController.php @@ -270,7 +270,7 @@ class GoogleAdsController public function getAds($options): Response { // $options['login_customer_id'] = 1401879025; - $options['refresh_token'] = '1//0eeLHHxVH5EK1CgYIARAAGA4SNwF-L9Irjf3gV-k7bjIeT7WWpbNrvvJQU2cM9OTPUnG4aBYIJDntlUzQ8T1egKkQco5lNtg28FA'; + $options['refresh_token'] = '1//0eeaBHt4kvLb1CgYIARAAGA4SNwF-L9IrnDelBOMlPpU-5W7Zvs7lfLW2XC5U0szxRM1_BkoNNVcWsAYc07Tf63dj1R0Ea-AtwZc'; $resourceName = $this->googleAdsAdService->runListAds($options['customer_id'],$options); return $this->successResponse(['groups_list' => $resourceName]); @@ -283,7 +283,7 @@ class GoogleAdsController public function getAssets($options): Response { $options['login_customer_id'] = 1401879025; - $options['refresh_token'] = '1//0en4AWGamrTnOCgYIARAAGA4SNwF-L9IrMOw2DkHF2y3DL2kDiEmOUzk5KTsR1f1iU_axBNi-LgxfT_76JHK4AY1KChZWfyNc0Qs'; + $options['refresh_token'] = ',1//0eeaBHt4kvLb1CgYIARAAGA4SNwF-L9IrnDelBOMlPpU-5W7Zvs7lfLW2XC5U0szxRM1_BkoNNVcWsAYc07Tf63dj1R0Ea-AtwZc'; $resourceName = $this->googleAdsAssetService->runListCreatives($options['customer_id'],$options); // return $this->successResponse(['assets_list' => $resourceName]); return $this->successResponse(['assets_list' => 'succeed added']); @@ -320,10 +320,10 @@ class GoogleAdsController public function getDateDatas($options): Response { $todayStart = date('Y-m-d', strtotime('0 day')); - $options['login_customer_id'] = 1401879025; - $options['refresh_token'] = '1//0en4AWGamrTnOCgYIARAAGA4SNwF-L9IrMOw2DkHF2y3DL2kDiEmOUzk5KTsR1f1iU_axBNi-LgxfT_76JHK4AY1KChZWfyNc0Qs'; + $options['login_customer_id'] = 2202747396; + $options['refresh_token'] = '1//0eeaBHt4kvLb1CgYIARAAGA4SNwF-L9IrnDelBOMlPpU-5W7Zvs7lfLW2XC5U0szxRM1_BkoNNVcWsAYc07Tf63dj1R0Ea-AtwZc'; // dump($todayStart); - $resourceName = $this->googleAdsCampaignService->runListDateDatas($options['customer_id'],$options, '2025-01-09'); + $resourceName = $this->googleAdsCampaignService->runListDateDatas($options['customer_id'],$options, '2024-12-21'); return $this->successResponse(['date_datas_list' => $resourceName]); } diff --git a/app/model/BpsAdLandingInsight.php b/app/model/BpsAdLandingInsight.php new file mode 100644 index 0000000..20336df --- /dev/null +++ b/app/model/BpsAdLandingInsight.php @@ -0,0 +1,32 @@ + 'int', + 'account_id' => 'string', + 'ad_id' => 'string', + 'landing_url' => 'string', + 'landing_access' => 'string', + 'create_at' => 'timestamp', + 'update_at' => 'timestamp', + ]; + + // 默认值设置 + protected $defaults = [ + 'landing_access' => '', + 'landing_url' => '', + ]; + +} diff --git a/app/service/LandingUrlInsightService.php b/app/service/LandingUrlInsightService.php new file mode 100644 index 0000000..4892b50 --- /dev/null +++ b/app/service/LandingUrlInsightService.php @@ -0,0 +1,279 @@ + 'UNSPECIFIED', // UNSPECIFIED + 1 => 'UNKNOWN', // UNKNOWN + 2 => 'ENABLED', // ENABLED + 3 => 'PAUSED', // PAUSED + 4 => 'REMOVED', // REMOVED + 5 => 'FROZEN', // FROZEN + 6 => 'ARCHIVED', // ARCHIVED + ]; + // 状态映射数组 + private static $platformMapping = [ + 1 => 'meta', + 2 => 'google', + 3 => 'tiktok', + ]; + // 状态映射数组 + private static $creativeTypeMapping = [ + 1 => 'image', + 2 => 'video', + ]; + + + public static function getLandingUrlInsightData($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $countOnly = false) + { + // 1. 创建查询对象,初始化 BpsAdCreativeInsight 查询 + $landingUrlDataQuery = BpsAdLandingUrl::alias('i') + ->join('bps.bps_ads_insights c', 'i.ad_id = c.ad_id', 'LEFT'); // 联接 bps_ads_creative 表 + if ($platformType != 0) { + $landingUrlDataQuery->where('i.platform_type', $platformType); // 只有 platformType 不为 0 时才添加筛选 + } + + // 2. 日期范围筛选 + if ($startDate && $endDate) { + $landingUrlDataQuery->whereBetween('c.date', [$startDate, $endDate]); + } + + // 3. 客户 ID 过滤(如果提供了) + if (!empty($customerIds)) { + $landingUrlDataQuery->whereIn('i.account_id', $customerIds); + } else { + return $countOnly ? 0 : [ + 'data' => [], + 'total' => 0, + 'statistics' => [], + 'pagination' => [ + 'startIndex' => 0, + 'maxResults' => $pageSize, + 'count' => 0, + 'pageNo' => 1, + 'pageSize' => $pageSize + ] + ]; + } + // 仅返回记录数时的逻辑 + if ($countOnly) { + return $landingUrlDataQuery->count('distinct(i.landing_url)') ?: 0; + } + + // 4. 关键词过滤 + if ($keyword) { +// $creativeDataQuery->where('c.name', 'like', '%' . $keyword . '%'); // 在 bps_ads_creative 表中查找关键词 + $landingUrlDataQuery->whereRaw('LOWER(i.landing_url) LIKE ?', ['%' . strtolower($keyword) . '%']); // 在 bps_ads_creative 表中查找关键词 + } + // 5. 数据聚合(按 landing_url 和其他字段) + $landingUrlDataQuery->group('i.landing_url, i.platform_type, i.account_id') // 按需要的字段分组 + ->field([ + 'i.landing_url', + 'i.platform_type', + 'i.account_id', + ThinkDb::raw('COALESCE(SUM(c.spend) / 1000000, 0) AS total_spend'), + ThinkDb::raw('COALESCE(SUM(c.purchases_value) / 1000000, 0) AS total_purchases_value'), + ThinkDb::raw('COALESCE(SUM(c.purchases), 0) AS total_purchases'), + ThinkDb::raw('COALESCE(SUM(c.revenue) / 1000000, 0) AS total_revenue'), + ThinkDb::raw('COALESCE(SUM(c.impressions), 0) AS total_impressions'), + ThinkDb::raw('COALESCE(SUM(c.clicks), 0) AS total_clicks'), + ThinkDb::raw('count(distinct(i.ad_id)) AS ad_count'), + ThinkDb::raw('-1 AS click_to_atc_ratio'), + ThinkDb::raw('-1 AS click_to_purchase'), + ThinkDb::raw('-1 AS thumbstop'), + ThinkDb::raw('-1 AS hold_rate'), + ThinkDb::raw('-1 AS ctr_outbound'),//Hook score Watch score Click score Convert score + ThinkDb::raw('-1 AS hook_score'), + ThinkDb::raw('-1 AS watch_score'), + ThinkDb::raw('-1 AS click_score'), + ThinkDb::raw('-1 AS convert_score'), + ThinkDb::raw('-1 AS ctr_link_click'), + ]); + + // 6. 执行查询并获取聚合结果 +// $aggregatedData = $creativeDataQuery->select(); + // 6. 执行查询并获取聚合结果 + $aggregatedData = $landingUrlDataQuery->select()->toArray(); // 转换为数组处理 +// dump($landingUrlDataQuery->getLastSql()); + $totalRecords = count($aggregatedData); // 总记录数 + + // 计算分页起始索引 + $startIndex = ($page - 1) * $pageSize; + + + // 打印调试 SQL 查询 +//$sql = $creativeDataQuery->getLastSql(); +// dump($sql); + + // 7. 初始化广告创意的汇总数据和统计数据 + $landingUrlSummaryData = []; +// $statisticsData = $this->initializeStatistics(); + $statisticsData = []; + + // 8. 遍历查询结果并计算每个 creative 的相关统计数据 + foreach ($aggregatedData as $landingData) { +// $landingUrl = self::normalizeUrl($landingData['landing_url']); + $landingUrlHash = self::generateHash($landingData['landing_url']); + if (!isset($creativeSummaryData[$landingUrlHash])) { + $landingUrlSummaryData[$landingUrlHash] = [ + 'landing_url' => $landingData['landing_url'], + 'platform_type' => $landingData['platform_type'], + 'account_id' => $landingData['account_id'], + 'total_conversions_value' => 0, + 'total_conversions' => 0, + 'impressions' => 0, + 'clicks' => 0, + 'revenue' => 0, + 'ad_count' => $landingData['ad_count'] ?: 0, + 'spend' => 0, + 'purchases' => 0, + 'purchases_value' => 0, + 'roas' => 0, + 'aov' => '-', + 'click_to_atc_ratio' => '-', + 'click_to_purchase' => '-', + 'thumbstop' => '-', + 'hold_rate' => '-', + 'ctr_outbound' => '-', + 'hook_score' => '-', + 'watch_score' => '-', + 'click_score' => '-', + 'convert_score' => '-', + 'ctr_link_click' => '-', + ]; + } + + // 更新该 landing_url 的统计数据 + $landingUrlSummaryData[$landingUrlHash]['spend'] += $landingData['total_spend']; + $landingUrlSummaryData[$landingUrlHash]['purchases_value'] += $landingData['total_purchases_value']; + $landingUrlSummaryData[$landingUrlHash]['purchases'] += $landingData['total_purchases']; + $landingUrlSummaryData[$landingUrlHash]['impressions'] += $landingData['total_impressions']; + $landingUrlSummaryData[$landingUrlHash]['clicks'] += $landingData['total_clicks']; + $landingUrlSummaryData[$landingUrlHash]['revenue'] += $landingData['total_revenue']; + // 计算 ROAS + $roas = $landingUrlSummaryData[$landingUrlHash]['spend'] > 0 + ? $landingUrlSummaryData[$landingUrlHash]['revenue'] / $landingUrlSummaryData[$landingUrlHash]['spend'] + : 0; + $landingUrlSummaryData[$landingUrlHash]['roas'] = $roas > 0 ? number_format($roas, 2) . 'X' : '-'; + + // 填充广告计数 +// $landingUrlSummaryData[$creativeData->creative_id]['ad_count'] = rand(10, 200); // 每个 creative_id 对应一个广告 + } +// dump($landingUrlSummaryData); + + // 汇总总体统计数据 + $statisticsData['spend'] = array_sum(array_column($landingUrlSummaryData, 'spend')); + $statisticsData['purchases_value'] = array_sum(array_column($landingUrlSummaryData, 'purchases_value')); + $statisticsData['purchases'] = array_sum(array_column($landingUrlSummaryData, 'purchases')); + $statisticsData['impressions'] = array_sum(array_column($landingUrlSummaryData, 'impressions')); + $statisticsData['clicks'] = array_sum(array_column($landingUrlSummaryData, 'clicks')); + $statisticsData['revenue'] = array_sum(array_column($landingUrlSummaryData, 'revenue')); + // 汇总统计数据 + $statistics = [ + 'spend' => '$' . number_format($statisticsData['spend'], 2), // 格式化金额 + 'purchases_value' => '$' . number_format($statisticsData['purchases_value'], 2), // 格式化金额 + 'purchases' => $statisticsData['purchases'], + 'click_to_atc_ratio' => '-', // 格式化百分比 + 'click_to_purchase' => '-',// 格式化百分比 + 'roas' => $statisticsData['spend'] == 0 ? '-' : round($statisticsData['revenue'] / $statisticsData['spend'], 2) . 'X', + 'aov' => '-', // 格式化金额 + 'thumbstop' => '-', + 'hold_rate' => '-',// 格式化百分比 + 'ctr_outbound' => '-', + 'cpa' => '-', // 格式化金额 + 'hook_score' => '-', + 'watch_score' => '-', + 'click_score' => '-', + 'convert_score' => '-', + 'ctr_link_click' => ($statisticsData['impressions'] > 0) ? number_format(($statisticsData['clicks'] / $statisticsData['impressions']) * 100, 2) . '%' : '-', // 格式化为百分比 + + ]; + + + // 格式化返回的创意数据 + $formattedData = array_map(function ($item) { + return [ + 'landing_url' => $item['landing_url'], + 'platform_type' => $item['platform_type'], + 'account_id' => $item['account_id'], + 'ad_count' => $item['ad_count'], + 'spend' => '$' . number_format($item['spend'], 2), + 'purchases' => $item['purchases'], + 'purchases_value' => '$' . number_format($item['purchases_value'], 2), + 'click_to_atc_ratio' => '-', + 'click_to_purchase' => '-',// 格式化百分比 + 'roas' => $item['roas'], + 'aov' => '-', + 'thumbstop' => '-', + 'hold_rate' => '-',// 格式化百分比 + 'ctr_outbound' => '-', + 'cpa' => '-', // 格式化金额 + 'hook_score' => '-', + 'watch_score' => '-', + 'click_score' => '-', + 'convert_score' => '-', + 'ctr_link_click' => ($item['impressions'] > 0) ? number_format(($item['clicks'] / $item['impressions']) * 100, 2) . '%' : '-', + + // 添加更多的格式化字段 + ]; + }, $landingUrlSummaryData); +// $formattedData = array_values($landingUrlSummaryData); // 未分页处理 + $pagedData = array_slice(array_values($formattedData), $startIndex, $pageSize); // 分页处理 + // 9. 返回分页数据 + return [ + 'data' => $pagedData, + 'total' => count($landingUrlSummaryData), + 'statistics' => $statistics, // 汇总的统计数据 + 'pagination' => [ + 'startIndex' => ($page - 1) * $pageSize, + 'maxResults' => $pageSize, + 'count' => count($landingUrlSummaryData), + 'pageNo' => $page, + 'pageSize' => $pageSize, + 'pages' => ceil(count($landingUrlSummaryData) / $pageSize) + ] + ]; + } + + public static function normalizeUrl($url) + { + $url = strtolower($url); // 统一小写 + $url = preg_replace('#^https?://(www\.)?#', '', $url); // 移除协议和 www + $url = rtrim($url, '/'); // 移除末尾的斜杠 + return $url; + } + + // 生成哈希值 + public static function generateHash($url) + { + return md5($url); // 使用 MD5 生成哈希值 + } + + + public static function getPlatformType($thirdType) + { + $platformMapping = [ + 'facebook' => 1, + 'google' => 2, + 'tiktok' => 3, + ]; + + return $platformMapping[$thirdType] ?? null; // 如果不存在的第三方类型,返回 null + } + +} diff --git a/config/route.php b/config/route.php index ddcbc6b..eeee537 100644 --- a/config/route.php +++ b/config/route.php @@ -89,6 +89,14 @@ Route::group('/marketing', function () { app\middleware\JwtLocal::class, app\middleware\OauthThirdCheck::class, ]); + Route::group('/landing', function () { + Route::post('/list', [BpsAdController::class, 'listLandingUrls']); +// Route::post('/export', [BpsAdController::class, 'exportLandingUrlsToExcel']); +// Route::post('/chart', [BpsAdController::class, 'listCharts']); + })->middleware([ + app\middleware\JwtLocal::class, + app\middleware\OauthThirdCheck::class, + ]); }); });