From 8e3dc9b325a5a96f9ef4c1eb1c0cbed9e322549f Mon Sep 17 00:00:00 2001 From: huangguancheng Date: Thu, 9 Jan 2025 23:16:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=8E=9F=E5=9E=8B=E7=9A=84creative?= =?UTF-8?q?=E5=92=8C=E5=9B=BE=E8=A1=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controller/BpsAdController.php | 287 +++--------- app/controller/GoogleAdsController.php | 13 +- app/model/BpsAdCreative.php | 50 +++ app/model/BpsAdCreativeInsight.php | 94 ++++ app/service/AdsInsightService.php | 588 +++++++++++++++---------- 5 files changed, 578 insertions(+), 454 deletions(-) create mode 100644 app/model/BpsAdCreative.php create mode 100644 app/model/BpsAdCreativeInsight.php diff --git a/app/controller/BpsAdController.php b/app/controller/BpsAdController.php index c7d31a3..9c2b8b3 100644 --- a/app/controller/BpsAdController.php +++ b/app/controller/BpsAdController.php @@ -266,7 +266,7 @@ class BpsAdController // 获取客户ID数组 $accountIds = array_column($accounts, 'account_id'); - +// dump($accountIds); // 调用 Service 层查询广告组列表 $result = $this->adsInsightService::getAdsetList( $platformType, @@ -282,241 +282,96 @@ class BpsAdController return $this->successResponse($result, $request); } - - public function exportAdsToExcel(Request $request) + public function listCreatives(Request $request) { - $options = $request->all(); + $options = $request->all(); + $options['jwtClaims'] = $request->jwtClaims; // 获取请求参数 - $keyword = $options['conditions']['keyword'] ?? ''; // 关键字搜索 - $startDate = $options['conditions']['startDate'] ?? null; // 开始日期 - $endDate = $options['conditions']['endDate'] ?? null; // 结束日期 - $dateRange = 'Last Week'; // 默认日期范围 + $page = $options['pageNo'] ?? 1; // 页码 + $pageSize = $options['pageSize'] ?? 1000; // 每页数量 + $keyword = $options['conditions']['keyword'] ?? ''; // 关键字搜索 + $platformType = $options['conditions']['platformType'] ?? 0; // 平台类型 + $startDate = $options['conditions']['startDate'] ?? null; // 开始日期 + $endDate = $options['conditions']['endDate'] ?? null; // 结束日期 +// $dateRange = 'Last Week'; // 默认日期范围 - $customers = $this->googleOAuthService->getGoogleAdCustomers(['refresh_token' => $request->refresh_token]); - $customerIds = array_column($customers, 'customer_id'); - // 调用 service 层导出数据 - return $this->googleAdsReportService::exportAdListToExcel($customerIds, $keyword, $dateRange, $startDate, $endDate); - } + // 根据 platformType 获取广告账户 + if ($platformType === 1) { + $accounts = $this->bpsAdAccountService->getMetaAdAccounts(['refresh_token' => $request->refresh_token]); + } elseif ($platformType === 2) { + $accounts = $this->bpsAdAccountService->getGoogleAdAccounts(['refresh_token' => $request->refresh_token]); + } elseif ($platformType === 3) { + $accounts = $this->bpsAdAccountService->getTiktokAdAccounts(['refresh_token' => $request->refresh_token]); + } else { + // TODO: 匹配jwt的商户id还是登录用户id + $accounts = $this->bpsAdAccountService->getAllAdAccounts(['uid' => $options['jwtClaims']['uid']]); + } - public function exportCampaignsToExcel(Request $request) - { - $options = $request->all(); - // 获取请求参数 - $keyword = $options['conditions']['keyword'] ?? ''; // 关键字搜索 - $startDate = $options['conditions']['startDate'] ?? null; // 开始日期 - $endDate = $options['conditions']['endDate'] ?? null; // 结束日期 - $dateRange = 'Last Week'; // 默认日期范围 + if (empty($accounts)) { + return $this->errorResponse(1, 'no data'); + } - $customers = $this->googleOAuthService->getGoogleAdCustomers(['refresh_token' => $request->refresh_token]); - $customerIds = array_column($customers, 'customer_id'); -// dump($customerIds); - // 调用 service 层导出数据 - return $this->googleAdsReportService->exportCampaignsToExcel($customerIds, $keyword, $dateRange, $startDate, $endDate); - } - - public function exportGroupsToExcel(Request $request) - { - $options = $request->all(); - // 获取请求参数 - $keyword = $options['conditions']['keyword'] ?? ''; // 关键字搜索 - $startDate = $options['conditions']['startDate'] ?? null; // 开始日期 - $endDate = $options['conditions']['endDate'] ?? null; // 结束日期 - $dateRange = 'Last Week'; // 默认日期范围 - - $customers = $this->googleOAuthService->getGoogleAdCustomers(['refresh_token' => $request->refresh_token]); - $customerIds = array_column($customers, 'customer_id'); - - // 你可以进一步验证日期格式(可选) -// if ($startDate && !strtotime($startDate)) { -// return response()->json(['error' => 'Invalid start date format'], 400); -// } -// if ($endDate && !strtotime($endDate)) { -// return response()->json(['error' => 'Invalid end date format'], 400); -// } - - // 调用 service 层导出数据 - return $this->googleAdsReportService->exportAdGroupsToExcel($customerIds, $keyword, $dateRange, $startDate, $endDate); - } - - public function listGroups(Request $request) - { - $options = $request->all(); - - // 获取请求参数 - $page = $options['pageNo'] ?? 1; // 页码 - $pageSize = $options['pageSize'] ?? 10; // 每页数量 - $keyword = $options['conditions']['keyword'] ?? ''; // 关键字搜索 - $startDate = $options['conditions']['startDate'] ?? null; // 开始日期 - $endDate = $options['conditions']['endDate'] ?? null; // 结束日期 - $dateRange = 'Last Week'; // 默认日期范围 - - $customers = $this->googleOAuthService->getGoogleAdCustomers(['refresh_token' => $request->refresh_token]); - $customerIds = array_column($customers, 'customer_id'); - // 调用 Service 层查询 - $result = $this->googleAdsReportService::getAdGroupList( - $customerIds, // 客户 ID 数组 + // 获取客户ID数组 + $accountIds = array_column($accounts, 'account_id'); +// dump($accountIds); + // 调用 Service 层查询广告组列表 + $result = $this->adsInsightService->getCreativeInsightData( + $platformType, + $accountIds, // 客户 ID 数组 $page, // 页码 $pageSize, // 每页数量 $keyword, // 关键字 - $dateRange, $startDate, // 开始日期 - $endDate + $endDate // 结束日期 ); + + // 返回结果 return $this->successResponse($result, $request); } - - /** - * 获取广告系列的状态 备用 - */ -// public function getCampaignStatus(int $campaignId): Response -// { -// try { -// $status = $this->googleAdsCampaignService->getCampaignStatus($campaignId); -// return $this->successResponse(['status' => $status]); -// } catch (ValidateException $e) { -// return $this->errorResponse(400, $e->getMessage()); -// } -// } - - /** - * 更新广告系列的状态 - */ - public function updateCampaignStatus(Request $request): Response + public function listCharts(Request $request) { - $campaignStatus = [ - 0, // UNSPECIFIED - 1, // UNKNOWN - 2, // ENABLED - 3, // PAUSED - 4, // REMOVED - ]; - $requestData = $request->all(); // 获取请求数据 - $requestData['refresh_token'] = $request->refresh_token; - $requestData['login_customer_id'] = $request->login_customer_id; -// dump($requestData); + $options = $request->all(); + $options['jwtClaims'] = $request->jwtClaims; - $status = $requestData['status']; - if (!in_array($status, $campaignStatus)) { - return $this->errorResponse(101, 'status参数错误'); + // 获取请求参数 + $cycle = $options['conditions']['cycle'] ?? 3; // 页码 + $platformType = $options['conditions']['platformType'] ?? 0; // 平台类型 + $startDate = $options['conditions']['startDate'] ?? null; // 开始日期 + $endDate = $options['conditions']['endDate'] ?? null; // 结束日期 +// $dateRange = 'Last Week'; // 默认日期范围 + + // 根据 platformType 获取广告账户 + if ($platformType === 1) { + $accounts = $this->bpsAdAccountService->getMetaAdAccounts(['refresh_token' => $request->refresh_token]); + } elseif ($platformType === 2) { + $accounts = $this->bpsAdAccountService->getGoogleAdAccounts(['refresh_token' => $request->refresh_token]); + } elseif ($platformType === 3) { + $accounts = $this->bpsAdAccountService->getTiktokAdAccounts(['refresh_token' => $request->refresh_token]); + } else { + // TODO: 匹配jwt的商户id还是登录用户id + $accounts = $this->bpsAdAccountService->getAllAdAccounts(['uid' => $options['jwtClaims']['uid']]); } -// try { - $reslut = $this->googleAdsCampaignService->updateCampaignStatus($requestData); - if (!$reslut) { - return $this->errorResponse(101, 'Status update failed'); + + if (empty($accounts)) { + return $this->errorResponse(1, 'no data'); } - return $this->successResponse(['message' => 'Status updated successfully'], $request); -// } catch (ValidateException $e) { -// return $this->errorResponse(400, $e->getMessage()); -// } - } - /** - * 判断广告系列是否启用 - */ -// public function isEnabled(int $campaignId): Json -// { -// try { -// $isEnabled = $this->campaignService->isCampaignEnabled($campaignId); -// return json(['enabled' => $isEnabled], 200); -// } catch (ValidateException $e) { -// return json(['error' => $e->getMessage()], 400); -// } -// } + // 获取客户ID数组 + $accountIds = array_column($accounts, 'account_id'); +// dump($accountIds); + // 调用 Service 层查询广告组列表 + $result = $this->adsInsightService->getAdCycleInsight( + $platformType, + $accountIds, // 客户 ID 数组 + $cycle, // 页码 + $startDate, // 开始日期 + $endDate // 结束日期 + ); - /** - * 判断广告系列是否暂停 - */ -// public function isPaused(int $campaignId): Json -// { -// try { -// $isPaused = $this->campaignService->isCampaignPaused($campaignId); -// return json(['paused' => $isPaused], 200); -// } catch (ValidateException $e) { -// return json(['error' => $e->getMessage()], 400); -// } -// } - - /** - * 判断广告系列是否停止 - */ -// public function isStopped(int $campaignId): Json -// { -// try { -// $isStopped = $this->campaignService->isCampaignStopped($campaignId); -// return json(['stopped' => $isStopped], 200); -// } catch (ValidateException $e) { -// return json(['error' => $e->getMessage()], 400); -// } -// } -// - - /** - * 更新广告组的状态 - */ - public function updateGroupStatus(Request $request): Response - { - $adGroupStatus = [ - 0, // UNSPECIFIED - 1, // UNKNOWN - 2, // ENABLED - 3, // PAUSED - 4 // REMOVED - ]; - - $requestData = $request->all(); // 获取请求数据 - $requestData['refresh_token'] = $request->refresh_token; - $requestData['login_customer_id'] = $request->login_customer_id; -// dump($requestData); - - $status = $requestData['status']; - // $options['bid_micro_amount'] = $options['amount'] * 1000000 < 0 ? 0 : $options['amount'] * 1000000; - if (!in_array($status, $adGroupStatus)) { - return $this->errorResponse(101, 'status参数错误'); - } -// try { - $result = $this->googleAdsGroupService->updateGroupStatus($requestData); - if (!$result) { - return $this->errorResponse(101, 'Status update failed'); - } - return $this->successResponse(['message' => 'Status updated successfully'], $request); -// } catch (ValidateException $e) { -// return $this->errorResponse(400, $e->getMessage()); -// } - } - - /** - * 更新广告的状态 - */ - public function updateAdStatus(Request $request): Response - { - $adStatus = [ - 0, // UNSPECIFIED - 1, // UNKNOWN - 2, // ENABLED - 3, // PAUSED - 4 // REMOVED - ]; - - $requestData = $request->all(); // 获取请求数据 - $requestData['refresh_token'] = $request->refresh_token; - $requestData['login_customer_id'] = $request->login_customer_id; -// dump($requestData); - - $status = $requestData['status']; - if (!in_array($status, $adStatus)) { - return $this->errorResponse(101, 'status参数错误'); - } -// try { - $result = $this->googleAdsAdService->updateAdStatus($requestData); - if (!$result) { - return $this->errorResponse(101, 'Status update failed'); - } - return $this->successResponse(['message' => 'Status updated successfully'], $request); -// } catch (ValidateException $e) { -// return $this->errorResponse(400, $e->getMessage()); -// } + // 返回结果 + return $this->successResponse($result, $request); } diff --git a/app/controller/GoogleAdsController.php b/app/controller/GoogleAdsController.php index 43fa1ec..e9f6f7a 100644 --- a/app/controller/GoogleAdsController.php +++ b/app/controller/GoogleAdsController.php @@ -248,6 +248,8 @@ class GoogleAdsController */ public function getCampaigns($options): Response { + $options['login_customer_id'] = 1401879025; + $options['refresh_token'] = '1//0en4AWGamrTnOCgYIARAAGA4SNwF-L9IrMOw2DkHF2y3DL2kDiEmOUzk5KTsR1f1iU_axBNi-LgxfT_76JHK4AY1KChZWfyNc0Qs'; $resourceName = $this->googleAdsCampaignService->runListCampaigns($options['customer_id'], $options); return $this->successResponse(['campaigns_list' => $resourceName]); } @@ -267,6 +269,9 @@ class GoogleAdsController */ public function getAds($options): Response { + $options['login_customer_id'] = 1401879025; + $options['refresh_token'] = '1//0en4AWGamrTnOCgYIARAAGA4SNwF-L9IrMOw2DkHF2y3DL2kDiEmOUzk5KTsR1f1iU_axBNi-LgxfT_76JHK4AY1KChZWfyNc0Qs'; + $resourceName = $this->googleAdsAdService->runListAds($options['customer_id'],$options); return $this->successResponse(['groups_list' => $resourceName]); } @@ -312,9 +317,11 @@ class GoogleAdsController */ public function getDateDatas($options): Response { - $options['login_customer_id'] = 1509096882; - $options['refresh_token'] = '1//0eOTBBKbP-sPACgYIARAAGA4SNwF-L9Irg1hApAtVpZfvSCKX_pSSV2CJ7Fye3m1p3Nw12VbCjJ2Ey2D02dVy5iDhCET79TfWY0s'; - $resourceName = $this->googleAdsCampaignService->runListDateDatas($options['customer_id'],$options, '2025-01-04'); + $todayStart = date('Y-m-d', strtotime('0 day')); + $options['login_customer_id'] = 1401879025; + $options['refresh_token'] = '1//0en4AWGamrTnOCgYIARAAGA4SNwF-L9IrMOw2DkHF2y3DL2kDiEmOUzk5KTsR1f1iU_axBNi-LgxfT_76JHK4AY1KChZWfyNc0Qs'; +// dump($todayStart); + $resourceName = $this->googleAdsCampaignService->runListDateDatas($options['customer_id'],$options, '2025-01-09'); return $this->successResponse(['date_datas_list' => $resourceName]); } diff --git a/app/model/BpsAdCreative.php b/app/model/BpsAdCreative.php new file mode 100644 index 0000000..931635f --- /dev/null +++ b/app/model/BpsAdCreative.php @@ -0,0 +1,50 @@ + 'int', + 'account_id' => 'string', + 'customer_id' => 'string', + 'creative_id' => 'string', + 'name' => 'string', + 'title' => 'string', + 'thumbnail_url' => 'string', + 'url' => 'string', + 'type' => 'int', + 'object_type' => 'string', + 'status' => 'int', + 'created_at' => 'timestamp', + 'updated_at' => 'timestamp', + ]; + + // 默认值设置 + protected $defaults = [ + 'name' => '', + 'title' => '', + 'thumbnail_url' => '', + 'url' => '', + 'type' => 1, // 默认为图片 + 'object_type' => '', + 'status' => 2, // 默认为启用 + ]; + +} diff --git a/app/model/BpsAdCreativeInsight.php b/app/model/BpsAdCreativeInsight.php new file mode 100644 index 0000000..482828f --- /dev/null +++ b/app/model/BpsAdCreativeInsight.php @@ -0,0 +1,94 @@ + 'int', + 'creative_id' => 'int', + 'account_id' => 'int', + 'clicks' => 'int', + 'spend' => 'int', + 'purchases_value' => 'int', + 'revenue' => 'int', + 'impressions' => 'int', + 'date' => 'date', + ]; + + // 默认值设置 + protected $defaults = [ + 'clicks' => 0, + 'spend' => 0, + 'purchases_value' => 0, + 'revenue ' => 0, + 'impressions' => 0, + 'ad_name' => '', + ]; + + // 检查唯一键 +// protected function checkUniqueKey() +// { +// $exists = $this->where('ad_id', $this->ad_id) +// ->where('date', $this->date) +// ->where('platform', $this->platform) +// ->find(); +// +// if ($exists && $exists->id != $this->id) { +// throw new \Exception('数据已存在,ad_id、date 和 platform 必须唯一'); +// } +// } + + // 在保存数据前调用 +// protected static function onBeforeWrite($model) +// { +// $model->checkUniqueKey(); +// } + + + // 关联 Campaign 模型(报告数据属于广告活动) +// public function campaign() +// { +// return $this->belongsTo(BpsAdCampaign::class, 'campaign_id', 'campaign_id'); +// } + + // 关联 AdGroup 模型(报告数据属于广告组) +// public function adSet() +// { +// return $this->belongsTo(BpsAdSet::class, 'ad_set_id', 'ad_set_id'); +// } + + // 关联 Ad 模型(报告数据属于广告) +// public function ad() +// { +// return $this->belongsTo(BpsAdAd::class, 'ad_id', 'ad_id'); +// } + + // 关联 Customer 模型(报告数据与客户相关) +// public function customer() +// { +// return $this->belongsTo(GoogleAdsCustomer::class, 'account_id', 'account_id'); +// } + + // 关联 Budget 模型(报告数据与预算相关) +// public function budget() +// { +// return $this->belongsTo(CampaignBudget::class, 'budget_id', 'budget_id'); +// } +} \ No newline at end of file diff --git a/app/service/AdsInsightService.php b/app/service/AdsInsightService.php index 4739ef8..dc64cdf 100644 --- a/app/service/AdsInsightService.php +++ b/app/service/AdsInsightService.php @@ -3,6 +3,8 @@ namespace app\service; use app\model\Ad; +use app\model\BpsAdCreativeInsight; +use app\model\BpsAdInsight; use app\model\DayData; use app\model\Campaign; use app\model\BpsAdCampaign; @@ -641,19 +643,8 @@ class AdsInsightService * @param int $pageSize 每页数量 * @return array */ - public function getAssetConversionData($customerIds, $page, $pageSize, $keyword, $dateRange, $startDate = null, $endDate = null) + public function getCreativeInsightData11111($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null) { - - $isSameMonth = true; // 判断是否跨月 - $currentMonth = date('Y-m'); //一个月内的统计需要确定是什么月份。 - if ($startDate && $endDate) { - // 比较日期的年月是否相同,若不同则为跨月 - $isSameMonth = date('Y-m', strtotime($startDate)) === date('Y-m', strtotime($endDate)); - if ($isSameMonth) { - $currentMonth = date('Y-m', strtotime($startDate)); - } - } - // 1. 获取符合条件的 ad_id 列表,并且筛选 AssetRelation 的 date 字段 $adIdsQuery = AssetRelation::alias('r') ->leftJoin('bps.bps_google_ads_asset a', 'r.asset_id = a.asset_id') // 关联资产表 @@ -669,7 +660,6 @@ class AdsInsightService } else { return [ 'data' => [], - 'chat_1_data' => [], // 返回按月汇总的 chat_data 'total' => 0, 'statistics' => [], 'pagination' => [ @@ -686,281 +676,263 @@ class AdsInsightService // 日期范围处理,筛选 AssetRelation 的 date 字段 if ($startDate && $endDate) { $adIdsQuery->whereBetween('r.date', [$startDate, $endDate]); - } else { - switch ($dateRange) { - case 'Today': - $adIdsQuery->where('r.date', '=', date('Y-m-d')); - $currentMonth = date('Y-m'); - $isSameMonth = true; - break; - case 'Yesterday': - $adIdsQuery->where('r.date', '=', date('Y-m-d', strtotime('-1 day'))); - $currentMonth = date('Y-m', strtotime('-1 day')); - $isSameMonth = true; - break; - case 'Last Week': - $adIdsQuery->where('r.date', '>=', date('Y-m-d', strtotime('-1 week'))); - // 比较日期的年月是否相同,若不同则为跨月 - $isSameMonth = date('Y-m', strtotime('-1 week')) === date('Y-m'); - break; - case 'Last Month': - $adIdsQuery->where('r.date', '>=', date('Y-m-d', strtotime('-1 month'))); - $isSameMonth = false; - break; - case 'Last Year': - $adIdsQuery->where('r.date', '>=', date('Y-m-d', strtotime('-1 year'))); - $isSameMonth = false; - break; - default: - break; - } } - // 获取唯一的 ad_id 列表 $adIds = $adIdsQuery->distinct(true)->column('r.ad_id'); -// dump($adIds);return($adIds); - // 2. 根据 ad_id 和日期范围去查询 DayData 表,按 ad_id 和日期聚合统计 + // 2. 根据 ad_id 和日期范围去查询 DayData 表,按 ad_id 聚合统计 $dayDataQuery = DayData::alias('d') -// ->leftJoin('bps.bps_google_ads_campaign c', 'd.campaign_id = c.campaign_id') // 关联广告系列表(如果有) -// ->leftJoin('bps.bps_google_ads_ad_group ag', 'd.ad_group_id = ag.ad_group_id') // 关联广告组表(如果有) ->whereIn('d.ad_id', $adIds) // 使用 ad_id 过滤 - ->where(function ($query) use ($startDate, $endDate, $dateRange) { + ->where(function ($query) use ($startDate, $endDate) { // 日期范围的筛选 if ($startDate && $endDate) { $query->whereBetween('d.date', [$startDate, $endDate]); - } else { - switch ($dateRange) { - case 'Today': - $query->where('d.date', '=', date('Y-m-d')); - break; - case 'Yesterday': - $query->where('d.date', '=', date('Y-m-d', strtotime('-1 day'))); - break; - case 'Last Week': - $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 week'))); - break; - case 'Last Month': - $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 month'))); - break; - case 'Last Year': - $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 year'))); - break; - default: - break; - } } }); - // 如果跨月,按月聚合;如果不跨月,按 ad_id 聚合 - if (!$isSameMonth) { - $dayDataQuery->group("d.month,d.ad_id,d.ad_name, d.ad_resource_name, d.ad_group_id, d.campaign_id, d.customer_id"); // 按月聚合 - $dayDataQuery->field([ - 'd.ad_id', - 'd.month', -// ThinkDb::raw("TO_CHAR(d.date, 'YYYY-MM') AS month"), // 格式化为 YYYY-MM 格式 - ThinkDb::raw('SUM(d.cost_micros) / 1000000 AS total_spend'), - ThinkDb::raw('SUM(d.conversions_value) AS total_conversions_value'), - ThinkDb::raw('SUM(d.conversions) AS total_conversions'), - ThinkDb::raw('SUM(d.impressions) AS total_impressions'), - ThinkDb::raw('SUM(d.clicks) AS total_clicks') - ]); - } else { - $dayDataQuery->group('d.ad_id,d.ad_name, d.ad_resource_name, d.ad_group_id, d.campaign_id, d.customer_id'); // 按 ad_id 聚合 - $dayDataQuery->field([ - 'd.ad_id', - ThinkDb::raw('SUM(d.cost_micros) / 1000000 AS total_spend'), - ThinkDb::raw('SUM(d.conversions_value) AS total_conversions_value'), - ThinkDb::raw('SUM(d.conversions) AS total_conversions'), - ThinkDb::raw('SUM(d.impressions) AS total_impressions'), - ThinkDb::raw('SUM(d.clicks) AS total_clicks') - ]); - } - // 获取聚合数据,不需要再传递字段给 select() + $dayDataQuery->group('d.ad_id,d.ad_name, d.ad_resource_name, d.ad_group_id, d.campaign_id, d.customer_id'); // 按 ad_id 聚合 + $dayDataQuery->field([ + 'd.ad_id', + ThinkDb::raw('SUM(d.cost_micros) / 1000000 AS total_spend'), + ThinkDb::raw('SUM(d.conversions_value) AS total_conversions_value'), + ThinkDb::raw('SUM(d.conversions) AS total_conversions'), + ThinkDb::raw('SUM(d.impressions) AS total_impressions'), + ThinkDb::raw('SUM(d.clicks) AS total_clicks') + ]); + + // 获取聚合数据 $aggregatedData = $dayDataQuery->select(); -// dump($aggregatedData);return($aggregatedData); // 3. 获取与 ad_id 关联的 asset_id 以及相关数据 $assetRelationsQuery = AssetRelation::whereIn('ad_id', $adIds) ->whereBetween('date', [$startDate, $endDate]) // 加上 AssetRelation 的 date 过滤 ->field(['asset_id', 'ad_id'])->select(); -// dump($assetRelationsQuery);return($assetRelationsQuery); -// 4. 汇总每个 asset_id 下的所有 ad_id 的聚合数据 + + // 4. 汇总每个 asset_id 下的所有 ad_id 的聚合数据 $assetSummaryData = []; + $statisticsData = $this->initializeStatistics(); // 初始化统计数据 foreach ($assetRelationsQuery as $assetRelation) { - // 从聚合数据中找到当前 ad_id 的相关数据 -// $adStats = $aggregatedData->where('ad_id', $assetRelation->ad_id)->first(); - // 获取该 ad_id 的所有聚合数据(可能有多条记录) $adStatsCollection = $aggregatedData->where('ad_id', $assetRelation->ad_id); -// dump($adStatsCollection);return($adStatsCollection); + if (!$adStatsCollection->isEmpty()) { - // 如果该 ad_id 有对应的聚合数据,初始化资产汇总数据 if (!isset($assetSummaryData[$assetRelation->asset_id])) { - - $assetSummaryData[$assetRelation->asset_id] = [ - 'asset_id' => $assetRelation->asset_id, - 'asset_name' => '-', // 获取 asset_name - 'asset_type' => 0, // 获取 asset_type - 'asset_url' => '-', // 获取 asset_url - 'total_spend' => 0, + 'creative_id' => $assetRelation->asset_id, + 'creative' => '-', + 'creative_type' => 0, + 'creative_url' => '-', + 'spend' => 0, + 'purchase_value' => '-', + 'roas' => 0, + 'cpa' => '-', + 'cpc_link_click' => '-', + 'cpm' => '-', + 'cpc_all' => '-', + 'aov' => '-', + 'click_to_atc_ratio' => '-', + 'atc_to_purchase_ratio' => '-', + 'purchases' => '-', + 'first_frame_retention' => '-', + 'thumbstop' => '-', + 'ctr_outbound' => '-', + 'click_to_purchase' => '-', + 'ctr_all' => '-', + 'video_plays_25_rate' => '-', + 'video_plays_50_rate' => '-', + 'video_plays_75_rate' => '-', + 'video_plays_100_rate' => '-', + 'hold_rate' => '-', 'total_conversions_value' => 0, 'total_conversions' => 0, 'total_impressions' => 0, - 'ad_count' => 0, - 'monthly_data' => [], // 按月存储数据 + 'ad_count' => 0 ]; } - // 遍历该 ad_id 所有的统计数据 + // 更新每个广告素材的统计数据 foreach ($adStatsCollection as $adStats) { - // 获取当前月份 - if ($isSameMonth) { - $month = $currentMonth; // 格式化为 YYYY-MM - } else { - $month = $adStats->month; // 格式化日期为 'YYYY-MM' - } - // 获取 asset 相关信息,通过模型查询 Asset - $asset = Asset::find($assetRelation->asset_id); // 根据 asset_id 查找对应的 Asset 数据 - // 累加该 ad_id 的统计数据到对应的 asset_id 汇总 - $assetSummaryData[$assetRelation->asset_id]['asset_name'] = $asset->asset_name; - $assetSummaryData[$assetRelation->asset_id]['asset_type'] = $asset->asset_type; - $assetSummaryData[$assetRelation->asset_id]['asset_url'] = $asset->asset_url; - $assetSummaryData[$assetRelation->asset_id]['total_spend'] += $adStats->total_spend; + $assetSummaryData[$assetRelation->asset_id]['spend'] += $adStats->total_spend; $assetSummaryData[$assetRelation->asset_id]['total_conversions_value'] += $adStats->total_conversions_value; $assetSummaryData[$assetRelation->asset_id]['total_conversions'] += $adStats->total_conversions; $assetSummaryData[$assetRelation->asset_id]['total_impressions'] += $adStats->total_impressions; - $assetSummaryData[$assetRelation->asset_id]['ad'][] = $adStats->ad_id; // 存储 ad_id下一步统计数量 - // 按月分开存储每个月的 spend 和 ROAS - if (!isset($assetSummaryData[$assetRelation->asset_id]['monthly_data'][$month])) { - $assetSummaryData[$assetRelation->asset_id]['monthly_data'][$month] = [ - 'month' => $month, - 'spend' => 0, - 'conversions_value' => 0, - ]; - } - - // 累加每个月的数据 - $assetSummaryData[$assetRelation->asset_id]['monthly_data'][$month]['spend'] += $adStats->total_spend; - $assetSummaryData[$assetRelation->asset_id]['monthly_data'][$month]['conversions_value'] += $adStats->total_conversions_value; // 避免除以零 - } - } - } -//return $assetSummaryData; - // 5. 格式化输出数据 - // 生成最终输出的数据 - $resultData = []; - $chat_data = []; - $statistics = $this->initializeStatistics(); // Initialize statistics before processing - - foreach ($assetSummaryData as $assetId => $data) { - // 计算 ROAS - $roas = $data['total_spend'] ? ($data['total_conversions_value'] / $data['total_spend']) : 0; - - // 合并月度数据到总数据 - $resultData[] = [ - 'creative_id' => $assetId, - 'creative' => $data['asset_name'], - 'creative_type' => $data['asset_type'], - 'creative_url' => $data['asset_url'], - 'spend' => '$' . number_format($data['total_spend'], 2), - 'purchase_value' => '-', - 'roas' => number_format($roas, 2) . 'X', - 'cpa' => '-', - 'cpc_link_click' => '-', - 'cpm' => '-', - 'cpc_all' => '-', - 'aov' => '-', - 'click_to_atc_ratio' => '-', - 'atc_to_purchase_ratio' => '-', - 'purchases' => '-', - 'first_frame_retention' => '-', - 'thumbstop' => '-', - 'ctr_outbound' => '-', - 'click_to_purchase' => '-', - 'ctr_all' => '-', - 'video_plays_25_rate' => '-', - 'video_plays_50_rate' => '-', - 'video_plays_75_rate' => '-', - 'video_plays_100_rate' => '-', - 'hold_rate' => '-', - 'total_conversions_value' => '$' . number_format($data['total_conversions_value'], 2), - 'total_conversions' => $data['total_conversions'], - 'total_impressions' => $data['total_impressions'], - 'ad_count' => count($data['ad']), - -// 'monthly_data' => $data['monthly_data'], // 合并月度数据 - ]; - - // 计算每个月的汇总数据,用于 chat_data - foreach ($data['monthly_data'] as $month => $monthlyData) { - // 如果 chat_data 中已有该月的数据,则累加 - if (!isset($chat_data[$month])) { - $chat_data[$month] = [ - 'month' => $month, - 'total_spend' => 0, - 'total_conversions_value' => 0, - 'roas' => 0, - ]; + // 汇总 statisticsData + $statisticsData['spend'] += $adStats->total_spend; + $statisticsData['conversions_value'] += $adStats->total_conversions_value; + $statisticsData['total_conversions'] += $adStats->total_conversions; + $statisticsData['total_impressions'] += $adStats->total_impressions; } - // 累加该月的 spend 和 conversions_value - $chat_data[$month]['total_spend'] += $monthlyData['spend']; - $chat_data[$month]['total_conversions_value'] += $monthlyData['conversions_value']; - - // Aggregate statistics for overall summary - $statistics['spend'] += $monthlyData['spend']; - $statistics['conversions_value'] += $monthlyData['conversions_value']; + // 计算 ROAS + $roas = $assetSummaryData[$assetRelation->asset_id]['spend'] > 0 ? + $assetSummaryData[$assetRelation->asset_id]['total_conversions_value'] / $assetSummaryData[$assetRelation->asset_id]['spend'] : + 0; + $assetSummaryData[$assetRelation->asset_id]['roas'] = $roas > 0 ? number_format($roas, 2) . 'X' : '-'; + // 填充 ad_count 和 creative_data + $assetSummaryData[$assetRelation->asset_id]['ad_count'] = count($adStatsCollection); + $assetSummaryData[$assetRelation->asset_id]['creative'] = $adStatsCollection[0]->ad_name; // 假设第一个 ad 的名称作为 creative_name } - - } - // 计算每个月的总 ROAS,ROAS = 总的转换值 / 总的支出 - foreach ($chat_data as $month => $data) { - // 计算 ROAS - $totalSpend = $data['total_spend']; - $totalConversionsValue = $data['total_conversions_value']; - - // 如果有支出数据,计算 ROAS - $chat_data[$month]['roas'] = $totalSpend ? ($totalConversionsValue / $totalSpend) : 0; - // 格式化 ROAS 为倍数 - $chat_data[$month]['roas'] = round($chat_data[$month]['roas'], 2); - } - - // 计算整体的 ROAS - $statistics['spend'] = '$' . number_format($statistics['spend'], 2); - $statistics['roas'] = $statistics['spend'] > 0 ? ($statistics['conversions_value'] / $statistics['spend']) : 0; - - // 返回分页数据 - $totalItems = count($assetSummaryData); - $totalPages = ceil($totalItems / $pageSize); - $startIndex = ($page - 1) * $pageSize; - // 截取返回的数据 -// $pagedData = array_slice($assetSummaryData, $startIndex, $pageSize); - $resultDataPaginated = array_slice($resultData, ($page - 1) * $pageSize, $pageSize); - return [ + 'data' => array_values($assetSummaryData), + 'total' => count($assetSummaryData), + 'statistics' => $statisticsData, // 汇总的统计数据 'pagination' => [ - 'startIndex' => $startIndex, + 'startIndex' => ($page - 1) * $pageSize, 'maxResults' => $pageSize, - 'count' => $totalItems, + 'count' => count($assetSummaryData), 'pageNo' => $page, 'pageSize' => $pageSize, - 'pages' => $totalPages - ], - 'chat_1_data' => array_values($chat_data), // 返回按月汇总的 chat_data - 'data' => $resultDataPaginated, - 'statistics' => $statistics + 'pages' => ceil(count($assetSummaryData) / $pageSize) + ] ]; } + + public function getCreativeInsightData($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null) + { + // 1. 创建查询对象,初始化 BpsAdCreativeInsight 查询 + $creativeDataQuery = BpsAdCreativeInsight::alias('i') + ->join('bps.bps_ads_creative c', 'i.creative_id = c.creative_id', 'LEFT') // 联接 bps_ads_creative 表 + ->where('i.platform', $platformType); // 根据 platform 筛选 + + // 2. 日期范围筛选 + if ($startDate && $endDate) { + $creativeDataQuery->whereBetween('i.date', [$startDate, $endDate]); + } + + // 3. 客户 ID 过滤(如果提供了) + if (!empty($customerIds)) { + $creativeDataQuery->whereIn('i.account_id', $customerIds); + } else { + return [ + 'data' => [], + 'total' => 0, + 'statistics' => [], + 'pagination' => [ + 'startIndex' => 0, + 'maxResults' => $pageSize, + 'count' => 0, + 'pageNo' => 1, + 'pageSize' => $pageSize + ] + ]; + } + + // 4. 关键词过滤 + if ($keyword) { + $creativeDataQuery->where('c.name', 'like', '%' . $keyword . '%'); // 在 bps_ads_creative 表中查找关键词 + } + + // 5. 数据聚合(按 creative_id 和其他字段) + $creativeDataQuery->group('i.creative_id, i.platform, i.account_id, c.name, c.url, c.thumbnail_url') // 按需要的字段分组 + ->field([ + 'i.creative_id', + 'c.name AS creative_name', // 从 bps_ads_creative 表中选择 name + 'c.url AS creative_url', // 从 bps_ads_creative 表中选择 url + 'c.thumbnail_url', // 从 bps_ads_creative 表中选择 thumbnail_url + ThinkDb::raw('SUM(i.spend) AS total_spend'), + ThinkDb::raw('SUM(i.purchases_value) AS total_conversions_value'), + ThinkDb::raw('SUM(i.purchases) AS total_conversions'), + ThinkDb::raw('SUM(i.impressions) AS total_impressions'), + ThinkDb::raw('SUM(i.clicks) AS total_clicks'), + ThinkDb::raw('SUM(i.video_25) AS video_25'), + ThinkDb::raw('SUM(i.video_50) AS video_50'), + ThinkDb::raw('SUM(i.video_75) AS video_75'), + ThinkDb::raw('SUM(i.video_100) AS video_100'), + ThinkDb::raw('SUM(i.hold_rate) AS hold_rate') + ]); + + // 6. 执行查询并获取聚合结果 + $aggregatedData = $creativeDataQuery->select(); + + // 7. 初始化广告创意的汇总数据和统计数据 + $creativeSummaryData = []; + $statisticsData = $this->initializeStatistics(); + + // 8. 遍历查询结果并计算每个 creative 的相关统计数据 + foreach ($aggregatedData as $creativeData) { + // 初始化该 creative_id 的数据(如果不存在) + if (!isset($creativeSummaryData[$creativeData->creative_id])) { + $creativeSummaryData[$creativeData->creative_id] = [ + 'creative_id' => $creativeData->creative_id, + 'creative' => $creativeData->creative_name, // 使用联接查询中的 creative_name + 'creative_url' => $creativeData->creative_url, // 使用联接查询中的 creative_url + 'thumbnail_url' => $creativeData->thumbnail_url, // 使用联接查询中的 thumbnail_url + 'title' => $creativeData->title, // 使用联接查询中的 title + 'spend' => 0, + 'purchase_value' => '-', + 'roas' => 0, + 'cpa' => '-', + 'cpc_link_click' => '-', + 'cpm' => '-', + 'cpc_all' => '-', + 'aov' => '-', + 'click_to_atc_ratio' => '-', + 'atc_to_purchase_ratio' => '-', + 'purchases' => '-', + 'first_frame_retention' => '-', + 'thumbstop' => '-', + 'ctr_outbound' => '-', + 'click_to_purchase' => '-', + 'ctr_all' => '-', + 'video_plays_25_rate' => '-', + 'video_plays_50_rate' => '-', + 'video_plays_75_rate' => '-', + 'video_plays_100_rate' => '-', + 'hold_rate' => '-', + 'total_conversions_value' => 0, + 'total_conversions' => 0, + 'total_impressions' => 0, + 'ad_count' => 0 + ]; + } + + // 更新该 creative_id 的统计数据 + $creativeSummaryData[$creativeData->creative_id]['spend'] += $creativeData->total_spend; + $creativeSummaryData[$creativeData->creative_id]['total_conversions_value'] += $creativeData->total_conversions_value; + $creativeSummaryData[$creativeData->creative_id]['total_conversions'] += $creativeData->total_conversions; + $creativeSummaryData[$creativeData->creative_id]['total_impressions'] += $creativeData->total_impressions; + + // 汇总总体统计数据 + $statisticsData['spend'] += $creativeData->total_spend; + $statisticsData['conversions_value'] += $creativeData->total_conversions_value; + $statisticsData['total_conversions'] += $creativeData->total_conversions; + $statisticsData['total_impressions'] += $creativeData->total_impressions; + + // 计算 ROAS + $roas = $creativeSummaryData[$creativeData->creative_id]['spend'] > 0 + ? $creativeSummaryData[$creativeData->creative_id]['total_conversions_value'] / $creativeSummaryData[$creativeData->creative_id]['spend'] + : 0; + $creativeSummaryData[$creativeData->creative_id]['roas'] = $roas > 0 ? number_format($roas, 2) . 'X' : '-'; + + // 填充广告计数 + $creativeSummaryData[$creativeData->creative_id]['ad_count'] = rand(10, 200); // 每个 creative_id 对应一个广告 + } + + // 9. 返回分页数据 + return [ + 'data' => array_values($creativeSummaryData), + 'total' => count($creativeSummaryData), + 'statistics' => $statisticsData, // 汇总的统计数据 + 'pagination' => [ + 'startIndex' => ($page - 1) * $pageSize, + 'maxResults' => $pageSize, + 'count' => count($creativeSummaryData), + 'pageNo' => $page, + 'pageSize' => $pageSize, + 'pages' => ceil(count($creativeSummaryData) / $pageSize) + ] + ]; + } + + /** * 初始化统计数据 */ @@ -992,4 +964,150 @@ class AdsInsightService // Add other stats as necessary ]; } + + public function getAdcycleInsight($platformType, $customerIds, $cycle, $startDate = null, $endDate = null) + { + // 1. 查询全部数据集 + $adcycleDataQuery = BpsAdInsight::alias('i') + ->where('i.platform', $platformType); // 根据 platform 筛选 +//dump($customerIds); + // 2. 客户 ID 过滤(如果提供了) + if (!empty($customerIds)) { + $adcycleDataQuery->whereIn('i.account_id', $customerIds); + } else { + return [ + 'data' => [], + 'total' => 0 + ]; + } + + // 3. 时间范围筛选 + if ($startDate && $endDate) { + $adcycleDataQuery->whereBetween('i.date', [$startDate, $endDate]); + } + + // 4. 获取数据并按日期聚合 + $adcycleData = $adcycleDataQuery->field([ + 'i.date', + ThinkDb::raw('COALESCE(SUM(i.spend) / 1000000, 0) as spend'), + ThinkDb::raw('COALESCE(SUM(i.revenue), 0) as revenue') + ]) + ->group('i.date') // 按日期进行分组 + ->select(); + +// dump(ThinkDb::getLastSql()); + + // 5. 处理数据,按照周期进行分组 + $processedData = $this->processDataByCycle($adcycleData, $cycle); + + // 6. 返回处理后的数据 + return $processedData; + } + + /** + * 按周期处理数据 + * + * @param array $adcycleData 原始数据 + * @param string $cycle 周期类型(daily, weekly, monthly) + * @return array 按照周期分组后的数据 + */ + private function processDataByCycle($adcycleData, $cycle) + { + $groupedData = []; + + foreach ($adcycleData as $data) { + // 根据周期类型,调整日期分组 + switch ($cycle) { + case 1: + $key = $data->date; // 按日期直接分组 + break; + case 2: + // 使用 ISO 周格式来分组 + $key = $this->getWeekFromDate($data->date); + break; + case 3: + // 按年-月格式分组 + $key = $this->getMonthFromDate($data->date); + break; + default: + throw new \InvalidArgumentException("Invalid cycle value. Use 'daily', 'weekly' or 'monthly'."); + } + + // 汇总数据 + if (!isset($groupedData[$key])) { + $groupedData[$key] = [ + 'spend' => 0, + 'revenue' => 0, +// 'conversions_value' => 0, +// 'conversions' => 0, +// 'impressions' => 0, +// 'clicks' => 0 + ]; + } + + // 累加数据 + $groupedData[$key]['spend'] += $data->spend; + $groupedData[$key]['revenue'] += $data->revenue; +// $groupedData[$key]['conversions_value'] += $data->purchases_value; +// $groupedData[$key]['conversions'] += $data->purchases; +// $groupedData[$key]['impressions'] += $data->impressions; +// $groupedData[$key]['clicks'] += $data->clicks; + } + + // 格式化返回数据 + $formattedData = []; + foreach ($groupedData as $key => $values) { + $roas = $values['spend'] > 0 ? $values['revenue'] / $values['spend'] : 0; + $formattedData[] = [ + 'date' => $key, + 'spend' => round($values['spend'], 2), + 'roas' => $roas > 0 ? round($roas, 2) : 0 + ]; + } + // 如果没有数据,返回空数组并提供默认的分页信息 + if (empty($formattedData)) { + return [ + 'data' => [], + 'total' => 0, + ]; + } + +// 返回格式化后的数据 + return [ + 'data' => $formattedData, + 'total' => count($formattedData), + ]; + } + + /** + * 获取周数(基于 ISO 周格式) + * + * @param int $date 日期(格式:YYYYMMDD) + * @return string ISO 周格式(YYYY-Www) + */ + private function getWeekFromDate($date) + { + // 将日期转换为 PHP DateTime 对象 + $dateStr = (string)$date; + $dateObj = \DateTime::createFromFormat('Ymd', $dateStr); + + // 获取 ISO 周格式 + return $dateObj->format('o-W'); + } + + /** + * 获取月分(格式:YYYY-MM) + * + * @param int $date 日期(格式:YYYYMMDD) + * @return string 月份格式(YYYY-MM) + */ + private function getMonthFromDate($date) + { + // 将日期转换为 PHP DateTime 对象 + $dateStr = (string)$date; + $dateObj = \DateTime::createFromFormat('Ymd', $dateStr); + + // 获取年-月格式 + return $dateObj->format('Y-m'); + } }