280 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| namespace app\service;
 | |
| 
 | |
| use app\model\Ad;
 | |
| use app\model\BpsAdCreativeInsight;
 | |
| use app\model\BpsAdAd;
 | |
| use app\model\AssetRelation;
 | |
| use app\model\BpsAdLandingUrl;
 | |
| use DateTime;
 | |
| use PhpOffice\PhpSpreadsheet\Spreadsheet;
 | |
| use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
 | |
| use think\db\exception\DbException;
 | |
| use think\facade\Db as ThinkDb;
 | |
| use support\Response;
 | |
| 
 | |
| class LandingUrlInsightService
 | |
| {
 | |
|     // 状态映射数组
 | |
|     private static $statusMapping = [
 | |
|         0 => '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
 | |
|     }
 | |
| 
 | |
| }
 | 
