diff --git a/app/controller/CustomerController.php b/app/controller/CustomerController.php new file mode 100644 index 0000000..4c62c77 --- /dev/null +++ b/app/controller/CustomerController.php @@ -0,0 +1,113 @@ +input('customer_id'); // 需要绑定的广告账号 ID + $thirdUserId = $request->input('third_user_id'); // bps_third_user的id + $googleOAuthService = new GoogleOAuthService(); + $hasThirdUser = $googleOAuthService->bindThirdUserAdvertiser($customerId, $thirdUserId); + if (!$hasThirdUser) { + return $this->errorResponse(300, 'Invalid state parameter'); + } + return $this->successResponse($hasThirdUser); + } + + public function getCustomerList(Request $request) + { + $thirdUserId = $request->input('third_user_id'); // bps_third_user的id + + $googleOAuthService = new GoogleOAuthService(); + $hasThirdUser = $googleOAuthService->getCustomerList($thirdUserId); + if (!$hasThirdUser) { + return $this->errorResponse(300, 'Invalid state parameter'); + } + return $this->successResponse($hasThirdUser); + + } + + + public function accessibleCustomers(Request $request) + { + $options = $request->all(); + // 继续处理 Google Ads API 操作 + return $this->listAccessibleCustomers($options); + } + + public function accountHierarchy(Request $request) + { + $options = $request->all(); + + // 继续处理 Google Ads API 操作 + return $this->getAccountHierarchy($options); + } + + + /** + * 关联广告客户ID + * @throws ApiException + */ + public function listAccessibleCustomers($options): Response + { + $resourceName = $this->googleAdsAccountService->runListAccessibleCustomers($options); + return $this->successResponse(['links_resource_name' => $resourceName]); + } + + /** + * 广告主体下层级账号 + * @throws ApiException + */ + public function getAccountHierarchy($options): Response + { + $resourceName = $this->googleAdsAccountService->runGetAccountHierarchy($options); + return $this->successResponse(['links_resource_name' => $resourceName]); + } + + + // 可以加入一些公共方法 + protected function successResponse($data): Response + { + return Json([ + 'code' => 0, + 'msg' => 'ok', + 'data' => $data, + ]); + } + + protected function errorResponse($code, $message, $data = []): Response + { + return Json([ + 'code' => $code, + 'msg' => $message ?: 'error', + 'data' => $data + ]); + } + + +} \ No newline at end of file diff --git a/app/controller/GoogleAdsController.php b/app/controller/GoogleAdsController.php index e7380cc..bfeb67f 100644 --- a/app/controller/GoogleAdsController.php +++ b/app/controller/GoogleAdsController.php @@ -2,8 +2,6 @@ namespace app\controller; -use app\model\Campaign as CampaignModel; -use app\model\CampaignBudget; use Google\ApiCore\ApiException; use support\Request; use app\service\GoogleAdsCampaignService; @@ -12,6 +10,7 @@ use app\service\GoogleAdsAdService; use app\service\GoogleAdsAssetService; use app\service\GoogleAdsAssetRelationService; use app\service\GoogleAdsAccountService; +//use app\service\GoogleOAuthService; use support\Response; use DI\Annotation\Inject; use think\facade\Db as ThinkDb; @@ -79,7 +78,7 @@ class GoogleAdsController public function listGroups(Request $request) { - $options = $request->all(); + $options = $request->all(); // 继续处理 Google Ads API 操作 return $this->getGroups($options); @@ -207,12 +206,6 @@ class GoogleAdsController return $this->addLinkManagerToClient($options); } - public function accessibleCustomers(Request $request) - { -// $options = $request->all(); - // 继续处理 Google Ads API 操作 - return $this->listAccessibleCustomers(); - } /** * 创建广告系列 @@ -248,15 +241,6 @@ class GoogleAdsController return $this->successResponse(['link_resource_name' => $resourceName]); } - /** - * 关联广告客户ID - * @throws ApiException - */ - public function listAccessibleCustomers(): Response - { - $resourceName = $this->googleAdsAccountService->runListAccessibleCustomers(); - return $this->successResponse(['links_resource_name' => $resourceName]); - } /** * get campaigns @@ -274,7 +258,7 @@ class GoogleAdsController */ public function getGroups($options): Response { - $resourceName = $this->googleAdsGroupService->runListGroups($options['customer_id']); + $resourceName = $this->googleAdsGroupService->runListGroups($options['customer_id'], $options); return $this->successResponse(['groups_list' => $resourceName]); } /** @@ -283,7 +267,7 @@ class GoogleAdsController */ public function getAds($options): Response { - $resourceName = $this->googleAdsAdService->runListAds($options['customer_id']); + $resourceName = $this->googleAdsAdService->runListAds($options['customer_id'],$options); return $this->successResponse(['groups_list' => $resourceName]); } diff --git a/app/controller/OAuthController.php b/app/controller/OAuthController.php index f355929..597bd85 100644 --- a/app/controller/OAuthController.php +++ b/app/controller/OAuthController.php @@ -7,11 +7,9 @@ use support\Request; use support\Response; use DI\Annotation\Inject; use app\model\ThirdUserAdvertiser; -use app\event\GoogleAdsCampaigns; -use app\event\GoogleAdsGroups; -use app\event\GoogleAdsAds; -use app\event\GoogleAdsDateDatas; -use app\event\GoogleAdsMaterials; +use app\model\ThirdUser; + +use app\event\GoogleAdsCustomers; use Webman\Event\Event; class OAuthController @@ -24,9 +22,10 @@ class OAuthController private $googleOAuthService; - public function getAuthCode() + public function getAuthCode(Request $request) { - $authUrl = $this->googleOAuthService->getAuthUrl(); + $state = $request->get('state') ?? $request->jwtClaims['uid']; + $authUrl = $this->googleOAuthService->getAuthUrl($state); return $this->successResponse([ 'url' => $authUrl, ]); @@ -35,30 +34,36 @@ class OAuthController public function handleCallback(Request $request) { -// $state = $request->input('state'); // 从Google回调中获取state - $code = $request->input('code'); // 授权码 + $state = $request->input('state'); // 从Google回调中获取state + $code = $request->input('code'); // 授权码 - // 验证state值是否与保存的值一致 -// if ($state !== $_SESSION['oauth_state']) { -// if ($state !== '7a7a9edad5b48c127b7c14fabe39fae0') { -// return $this->errorResponse(400, 'Invalid state parameter'); -// } + if (!$state) { + return $this->errorResponse(300, 'Invalid state parameter'); + } // state值验证通过,继续处理授权码 $googleOAuthService = new GoogleOAuthService(); - $tokens = $googleOAuthService->getRefreshToken($code); + + $tokens = $googleOAuthService->getRefreshToken($code); if (!isset($tokens['refresh_token'])) { return $this->errorResponse(300, 'Invalid state parameter'); - }else{ - // 保存refresh token到数据库 - $googleOAuthService->updateRefreshToken($tokens['refresh_token'], $tokens['access_token']); - // 根据新用户绑定插入新数据todo -// if(!$result) { -// $googleOAuthService->saveRefreshToken($tokens['refresh_token'], $tokens['access_token']); -// } - + } else { + $googleOAuthService->saveRefreshToken($tokens['refresh_token'], $state); } - +// if (getenv('GOOGLE_DEVELOP_TOKEN_LEVEL') === 'test') { +// $option['manager_customer_id'] = '1401879025'; //开发者 +// $option['login_customer_id'] = '1401879025'; +// } +// $option['refresh_token'] = $tokens['refresh_token']; +// +// $thirdUser = ThirdUser::where('access_token', $tokens['refresh_token'])->find(); // 获取第一个结果 +// if ($thirdUser) { +// $option['third_user_id'] = $thirdUser->id; +// Event::emit(GoogleAdsCustomers::CUSTOMERADD, $option); +// } else { +// return $this->errorResponse(300, 'Invalid state parameter'); +// } return $this->successResponse($tokens); + } public function getRefreshToken(Request $request) @@ -93,34 +98,55 @@ class OAuthController public function revokeRefreshToken(Request $request) { // $accessToken = $request->input('token'); //access token -// $customerId = isset($requestData['ad_customer_id']) ? $requestData['ad_customer_id'] : getenv('GOOGLE_ADS_CUSTOMER_ID'); - $customerId = getenv('GOOGLE_ADS_CUSTOMER_ID'); //临时指定 +// $customerId = isset($requestData['customer_id']) ? $requestData['customer_id'] : getenv('GOOGLE_ADS_CUSTOMER_ID'); +// $customerId = getenv('GOOGLE_ADS_CUSTOMER_ID'); //临时指定 + + $uid = $request->input('user_id'); + // 通过 advertiser_id 查询 ThirdUserAdvertiser,联表查询 ThirdUser 数据 - $userAdvertiser = ThirdUserAdvertiser::with('googleUser') // 联表查询 user 关联 - ->where('advertiser_id', $customerId) // 根据 advertiser_id 查询 - ->find(); // 获取第一个结果 + $thirdUser = ThirdUser::where('user_id', $uid)->find(); // 获取第一个结果 +// dump($thirdUser); return ($uid); + if (!$thirdUser) { + return $this->errorResponse(300, '未授权'); + } // dump($userAdvertiser->googleUser->access_token); - $accessToken = $userAdvertiser->googleUser->access_token; + $accessToken = $thirdUser->access_token; $googleOAuthService = new GoogleOAuthService(); - $googleOAuthService->revokeToken($accessToken); + $googleOAuthService->revokeToken($accessToken, $thirdUser->id); return $this->successResponse(['deleted' => 'success']); } + //保存某个主体的全部access广告账号 + function saveAdvertisers(Request $request) + { + $customerIds = $request->input('third_user_list_customers'); // customer_id_list每个元素包含advertiser_id + + +// $thirdUserId = $request->input('third_user_id'); // bps_third_user的id + $googleOAuthService = new GoogleOAuthService(); + $hasThirdUser = $googleOAuthService->bindThirdUserAdvertiser($customerId, $thirdUserId); + if (!$hasThirdUser) { + return $this->errorResponse(300, 'Invalid state parameter'); + } + return $this->successResponse($hasThirdUser); + } + + public function testRefreshToken(Request $request) { // 接建立socket连到内部推送端口 $client = stream_socket_client('tcp://192.168.21.27:22101', $errorCode, $errorMessage); if (false === $client) { - throw new \Exception('rpc failed to connect: '.$errorMessage); + throw new \Exception('rpc failed to connect: ' . $errorMessage); } $rpc_request = [ - 'class' => 'Auth', - 'method' => 'ValidateJwtToken', - 'args' => [ + 'class' => 'Auth', + 'method' => 'ValidateJwtToken', + 'args' => [ [ 'uid' => 2023, 'username' => 'Tinywan', @@ -128,7 +154,7 @@ class OAuthController ] ]; // 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符 - fwrite($client, json_encode($rpc_request)."\n"); + fwrite($client, json_encode($rpc_request) . "\n"); // 读取推送结果 $result = fgets($client, 10240000); // 解析JSON字符串 diff --git a/app/event/GoogleAdsAds.php b/app/event/GoogleAdsAds.php index 5b18df7..2d9a9c5 100644 --- a/app/event/GoogleAdsAds.php +++ b/app/event/GoogleAdsAds.php @@ -55,9 +55,9 @@ class GoogleAdsAds public function getAds($options) { $customers = $this->googleOAuthService->getGoogleAdCustomers([]); - foreach ($customers as $customerId) { - $googleAdsAdService = new GoogleAdsAdService($customerId); - $resourceName = $googleAdsAdService->runListAds($customerId); + foreach ($customers as $customer) { + $googleAdsAdService = new GoogleAdsAdService($customer['customer_id']); + $resourceName = $googleAdsAdService->runListAds($customer['customer_id'],$customer); } // return $this->successResponse(['ads_list' => $resourceName]); diff --git a/app/event/GoogleAdsAssetRelations.php b/app/event/GoogleAdsAssetRelations.php index b6d2e93..d10498e 100644 --- a/app/event/GoogleAdsAssetRelations.php +++ b/app/event/GoogleAdsAssetRelations.php @@ -56,9 +56,10 @@ class GoogleAdsAssetRelations public function getAssetRelations($options) { $customers = $this->googleOAuthService->getGoogleAdCustomers([]); - foreach ($customers as $customerId) { - $googleAdsAssetRelationService = new GoogleAdsAssetRelationService($customerId); - $resourceName = $googleAdsAssetRelationService->runListAssetRelations($customerId); + foreach ($customers as $customer) { +// dump($customer); + $googleAdsAssetRelationService = new GoogleAdsAssetRelationService($customer['customer_id']); + $resourceName = $googleAdsAssetRelationService->runListAssetRelations($customer['customer_id']); } // return $this->successResponse(['ads_list' => $resourceName]); @@ -70,9 +71,9 @@ class GoogleAdsAssetRelations public function getVideoAssetRelations($options) { $customers = $this->googleOAuthService->getGoogleAdCustomers([]); - foreach ($customers as $customerId) { - $googleAdsAssetRelationService = new GoogleAdsAssetRelationService($customerId); - $resourceName = $googleAdsAssetRelationService->runListVideoAssetRelations($customerId); + foreach ($customers as $customer) { + $googleAdsAssetRelationService = new GoogleAdsAssetRelationService($customer['customer_id']); + $resourceName = $googleAdsAssetRelationService->runListVideoAssetRelations($customer['customer_id']); } // return $this->successResponse(['ads_list' => $resourceName]); diff --git a/app/event/GoogleAdsAssets.php b/app/event/GoogleAdsAssets.php index 797ac5b..0a563fc 100644 --- a/app/event/GoogleAdsAssets.php +++ b/app/event/GoogleAdsAssets.php @@ -55,9 +55,9 @@ class GoogleAdsAssets public function getAssets($options) { $customers = $this->googleOAuthService->getGoogleAdCustomers([]); - foreach ($customers as $customerId) { - $googleAdsAssetService = new GoogleAdsAssetService($customerId); - $resourceName = $googleAdsAssetService->runListAssets($customerId); + foreach ($customers as $customer) { + $googleAdsAssetService = new GoogleAdsAssetService($customer['customer_id']); + $resourceName = $googleAdsAssetService->runListAssets($customer['customer_id'],$customer); } // return $this->successResponse(['ads_list' => $resourceName]); diff --git a/app/event/GoogleAdsCampaigns.php b/app/event/GoogleAdsCampaigns.php index b7d626e..7b0e15e 100644 --- a/app/event/GoogleAdsCampaigns.php +++ b/app/event/GoogleAdsCampaigns.php @@ -59,9 +59,10 @@ class GoogleAdsCampaigns public function getCampaigns($options) { $customers = $this->googleOAuthService->getGoogleAdCustomers([]); - foreach ($customers as $customerId) { - $googleAdsCampaignService = new googleAdsCampaignService($customerId); - $resourceName = $googleAdsCampaignService->runListCampaigns($customerId); +// dump($customers); + foreach ($customers as $customer) { + $googleAdsCampaignService = new googleAdsCampaignService($customer['customer_id']); + $resourceName = $googleAdsCampaignService->runListCampaigns($customer['customer_id'],$customer); } // return $this->successResponse(['campaigns_list' => $resourceName]); } diff --git a/app/event/GoogleAdsCustomers.php b/app/event/GoogleAdsCustomers.php new file mode 100644 index 0000000..57e7d02 --- /dev/null +++ b/app/event/GoogleAdsCustomers.php @@ -0,0 +1,325 @@ +where('third_type', 'google') + ->find(); + if (getenv('GOOGLE_DEVELOP_TOKEN_LEVEL') === 'test') { + $option['manager_customer_id'] = '1401879025'; //开发者 + $option['login_customer_id'] = '1401879025'; + } + $option['refresh_token'] = $thirdUser->access_token; +// dump($option); + $allRootAccounts = $this->googleAdsAccountService->runGetAccountHierarchy($option); +// dump($allAccounts); + foreach ($allRootAccounts as $rootAccountId => $accounts) { +// dump($rootAccountId, $accounts); + foreach ($accounts as $account) { + $customerId = $account['customer_id']; + $customerName = $account['descriptive_name']; +// dump($customerId, $customerName, $thirdUser->id); + $this->googleOAuthService->saveThirdUserAdvertiser($customerId, $thirdUser->id,$rootAccountId, $customerName); + } + + } +// return $this->successResponse($allAccounts); + + } + + + public function listCustomers(Request $request) + { + $options = $request->all(); + + // 继续处理 Google Ads API 操作 + return $this->getCustomers($options); + } + + /** + * get assets + * @throws ApiException + */ + public function getAccountHierarchy($options) + { + $resourceName = $this->googleAdsAccountService->runGetAccountHierarchy($options); + return $this->successResponse(['links_resource_name' => $resourceName]); + + + $customers = $this->googleOAuthService->getGoogleAdCustomers([]); + foreach ($customers as $customerId) { + $googleAdsAssetService = new GoogleAdsAssetService($customerId); + $resourceName = $googleAdsAssetService->runListAssets($customerId); + } + +// return $this->successResponse(['ads_list' => $resourceName]); + } + + /** + * 每天爬取tiktok广告 + * @return void + */ + public function update() + { + try { + + $client = new Client([ + //允许重定向 +// 'allow_redirects' => true, + ]); + + + // 获取前两天 0 点的时间戳 + $dayBeforeYesterdayStart = strtotime('-2 days 00:00:00'); + + // 获取前一天 0 点的时间戳 + $yesterdayStart = strtotime('-1 day 00:00:00'); + $countryCache = Redis::get(self::type . 'lastCountry'); + //全部跑完跳出 + if ($countryCache == 'All') { + dump($countryCache . '国家更新tiktok Ads 完成'); + return; + } + + if (empty($countryCache)) { + $countryCache = 'GB'; + } + + $searchIdCache = Redis::get(self::type . 'lastSearchId'); + $offsetCache = Redis::get(self::type . 'nextOffset'); + $totalCache = Redis::get(self::type . 'totalCache'); + if (!isset($searchIdCache) || empty($searchIdCache)) { + $searchIdCache = ''; + } + if (!isset($offsetCache) || empty($searchIdCache)) { + $offsetCache = 0; + } + if (!isset($totalCache) || empty($searchIdCache)) { + $totalCache = 0; + } + + //判断国家是否跑完。 + if ($totalCache > 0 && $offsetCache > ceil($totalCache / self::limit)) { + $key = array_search($countryCache, self::countries, true); + if (in_array($countryCache, self::countries) && isset(self::countries[$key + 1])) { + $countryCache = self::countries[$key + 1]; + dump('更新' . $countryCache . '国家的tiktok Ads 完成'); + } else { + $countryCache = 'All'; //赋值非正常国家的code中断定时任务 + dump($countryCache . '国家更新tiktok Ads 完成'); + } + return; + } + + + $start_time = $dayBeforeYesterdayStart; + $end_time = $yesterdayStart; + //当前国家爬取 + $currentParams = null; + $currentParams = ['country' => $countryCache, 'search_id' => $searchIdCache, 'type' => 1, 'start_time' => $start_time, 'end_time' => $end_time]; + + + $url = 'https://library.tiktok.com/api/v1/search?region=' . $currentParams['country'] . '&type=' . $currentParams['type'] . '&start_time=' . $currentParams['start_time'] . '&end_time=' . $currentParams['end_time']; + + $res = json_decode($client->post($url, [ + 'headers' => [ + 'accept' => 'application/json, text/plain, */*', + 'accept-language' => 'zh-CN,zh;q=0.9', + 'content-type' => 'application/json', + 'cookie' => 'cookie: _ttp=2ov8Fc4C2CaNscHJd90O9fMhlpE; _ga=GA1.1.1025820618.1731926196; FPID=FPID2.2.Bcgkp%2Fk%2Bbn5w5YeSMR9wd9VpNHJwTUpkkaEqSdCEa0w%3D.1731926196; FPAU=1.2.944915349.1731926193; FPLC=mbVyryI5aG6IVpAvhs1JsgWjA7FVA6QsCJ7VbXhM7zWoXNp4rcD0IK7FNTTf%2FuOrqeOgqEhTd4NB3hY7q3aDVTGQa3WGHqxkGte4%2BBZxsrpaHFas9kb7DPRXM12T5Q%3D%3D; _ga_TEQXTT9FE4=GS1.1.1732097542.7.0.1732097542.0.0.857840528', + 'origin' => 'https://library.tiktok.com', + 'priority' => 'u=1, i', + 'referer' => 'https://library.tiktok.com/ads?region=AT&start_time=1731945600000&end_time=1732032000000&adv_name=&adv_biz_ids=&query_type=&sort_type=last_shown_date,desc', + 'sec-ch-ua' => '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', + 'sec-ch-ua-mobile' => '?0', + 'sec-ch-ua-platform' => '"Windows"', + 'sec-fetch-dest' => 'empty', + 'sec-fetch-mode' => 'cors', + 'sec-fetch-site' => 'same-origin', + 'user-agent' => self::userAgent, +// 'user-agent' => 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + ], + 'json' => [ + 'query' => '', + 'query_type' => '', + 'adv_biz_ids' => '', + 'order' => self::sort_order, + 'offset' => (int)$offsetCache, + 'search_id' => $currentParams['search_id'], + 'limit' => self::limit, + ], + + ])->getBody()->getContents(), true); +// dump($res);return; //调试点 + + if ($res['search_id'] != $searchIdCache) { + $searchIdCache = $res['search_id']; + dump('search_id更新 ' . $searchIdCache . ' 成功'); + } + + + if ($res['total'] == 0 || $res['code'] != 0) { + dump('更新tiktok Ads接口响应异常:' . json_encode($res, JSON_UNESCAPED_UNICODE)); + return; + } + $listAdsIds = []; + foreach ($res['data'] as $ad) { + if ($ad['audit_status'] == 2 || empty($ad['videos'])) { + continue; //审核不过或者没视频不采集 + } + + $imagesJson = is_array($ad['image_urls']) ? json_encode($ad['image_urls']) : json_encode([]); + $rejection_info = is_array($ad['rejection_info']) ? json_encode($ad['rejection_info']) : null; +// dump($rejection_info); + $insertData[$ad['id']] = [ + 'ad_id' => $ad['id'], + 'name' => $ad['name'], + 'audit_status' => $ad['audit_status'], + 'type' => $ad['type'], + 'first_shown_date' => $ad['first_shown_date'], + 'last_shown_date' => $ad['last_shown_date'], + 'image_urls' => $imagesJson, + 'estimated_audience' => $ad['estimated_audience'], + 'spent' => $ad['spent'], + 'impression' => $ad['impression'], + 'show_mode' => $ad['show_mode'], + 'rejection_info' => $rejection_info, + 'sor_audit_status' => $ad['sor_audit_status'], + 'country_code' => $countryCache, + ]; + if (isset($ad['videos']) && !empty($ad['videos'])) { + // 遍历 "videos" 数组 + foreach ($ad['videos'] as $video) { + $insertData[$ad['id']]['video_url'] = $video['video_url']; + $insertData[$ad['id']]['cover_img'] = $video['cover_img']; + } + } + $listAdsIds = array_column($insertData, 'ad_id'); + + } +// dump($insertData);return; + + + if (empty($insertData)) return; + + //开启事务 + Db::beginTransaction(); + + + //删除原来的旧数据 + TiktokAd::query()->whereIn('ad_id', array_keys($insertData))->delete(); + //添加新的数据 + TiktokAd::query()->insert($insertData); + + //redis缓存 + Redis::set(self::type, json_encode($insertData, JSON_UNESCAPED_UNICODE)); + + //redis缓存 记录更新时间 + $time = date('Y-m-d H:i:s'); + Redis::set(self::type . 'time', $time); + Redis::set(self::type . 'lastCountry', $countryCache); + Redis::set(self::type . 'nextOffset', ++$offsetCache); //记录下一次的offset + Redis::set(self::type . 'totalCache', $res['total']); + Redis::set(self::type . 'lastSearchId', $searchIdCache); + if (!empty($listAdsIds)) { + Redis::rPush(self::type . 'AdsIds', ...$listAdsIds); + } + + //提交事务 + Db::commit(); + //销毁$res + unset($res); + + dump(date('Y-m-d H:i:s') . '更新' . self::type . '成功'); + } catch (GuzzleException|\Exception $exception) { + //回滚事务 + Db::rollBack(); + dump('更' . self::type . '异常:' . $exception->getMessage()); + dump($exception); + } +// } catch (ClientExceptionInterface $e) { +// // 捕获 4xx 错误 +// dump( 'Client error: ' . $e->getMessage() . "\n"); +// } catch (ServerExceptionInterface $e) { +// // 捕获 5xx 错误 +// dump('Server error: ' . $e->getMessage() . "\n"); +// } catch (TransportExceptionInterface $e) { +// // 捕获网络传输错误 +// dump('Transport error: ' . $e->getMessage() . "\n") ; +// } catch (\Exception $e) { +// // 捕获所有其他错误 +// dump('General error: ' . $e->getMessage() . "\n") ; +// } + } + + + // 可以加入一些公共方法 + protected function successResponse($data): Response + { + return Json([ + 'code' => 0, + 'msg' => 'ok', + 'data' => $data, + ]); + } + + protected function errorResponse($code, $message, $data = []): Response + { + return Json([ + 'code' => $code, + 'msg' => $message ?: 'error', + 'data' => $data + ]); + } +} diff --git a/app/event/GoogleAdsGroups.php b/app/event/GoogleAdsGroups.php index 7591d31..ffc1ef8 100644 --- a/app/event/GoogleAdsGroups.php +++ b/app/event/GoogleAdsGroups.php @@ -58,10 +58,9 @@ class GoogleAdsGroups { $customers = $this->googleOAuthService->getGoogleAdCustomers([]); - foreach ($customers as $customerId) { - $googleAdsGroupService = new GoogleAdsGroupService($customerId); - - $resourceName = $googleAdsGroupService->runListGroups($customerId); + foreach ($customers as $customer) { + $googleAdsGroupService = new GoogleAdsGroupService($customer['customer_id']); + $resourceName = $googleAdsGroupService->runListGroups($customer['customer_id'],$customer); // return $this->successResponse(['groups_list' => $resourceName]); } diff --git a/app/model/ThirdUser.php b/app/model/ThirdUser.php index 10ae3ca..ca53ac1 100644 --- a/app/model/ThirdUser.php +++ b/app/model/ThirdUser.php @@ -2,6 +2,7 @@ namespace app\model; use think\Model; +use app\model\ThirdUserAdvertiser; class ThirdUser extends Model { @@ -25,4 +26,11 @@ class ThirdUser extends Model { return $this->hasMany(ThirdUserAdvertiser::class, 'doc_', 'id'); } + + // 使用 onBeforeDelete 事件来级联删除广告主记录 +// public static function onBeforeDelete($thirdUser) +// { +// // 在删除 ThirdUser 时,删除所有与之关联的 ThirdUserAdvertiser 记录 +// $thirdUser->advertisers()->delete(); +// } } \ No newline at end of file diff --git a/app/process/UpdateGoogleAdsTask.php b/app/process/UpdateGoogleAdsTask.php index 4d51a8e..39e40e2 100644 --- a/app/process/UpdateGoogleAdsTask.php +++ b/app/process/UpdateGoogleAdsTask.php @@ -4,6 +4,7 @@ namespace app\process; use app\event\GoogleAdsCampaigns; +use app\event\GoogleAdsCustomers; use app\event\GoogleAdsGroups; use app\event\GoogleAdsAds; use app\event\GoogleAdsAssets; @@ -49,36 +50,36 @@ class UpdateGoogleAdsTask } ); // 每15分钟执行一次 - new Crontab('40 */15 * * * *', function () { -// dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsCampaigns::type . '开始'); -// Event::emit(GoogleAdsCampaigns::type, []); + new Crontab('15 */10 * * * *', function () { + dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsCampaigns::type . '开始'); + Event::emit(GoogleAdsCampaigns::type, []); } ); // 每15分钟执行一次 - new Crontab('50 */15 * * * *', function () { + new Crontab('30 */10 * * * *', function () { dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsGroups::type . '开始'); Event::emit(GoogleAdsGroups::type, []); } ); // 每15分钟执行一次 - new Crontab('55 */15 * * * *', function () { -// dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAds::type . '开始'); -// Event::emit(GoogleAdsAds::type, []); + new Crontab('5 */11 * * * *', function () { + dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAds::type . '开始'); + Event::emit(GoogleAdsAds::type, []); } ); // 每15分钟执行一次 - new Crontab('25 */15 * * * *', function () { -// dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAssets::type . '开始'); -// Event::emit(GoogleAdsAssets::type, []); + new Crontab('25 */19 * * * *', function () { + dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAssets::type . '开始'); + Event::emit(GoogleAdsAssets::type, []); } ); - new Crontab('55 */50 * * * *', function () { -// dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAssetRelations::IMAGEASSET . '开始'); -// Event::emit(GoogleAdsAssetRelations::IMAGEASSET, []); + new Crontab('*/20 * * * * *', function () { + dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAssetRelations::IMAGEASSET . '开始'); + Event::emit(GoogleAdsAssetRelations::IMAGEASSET, []); }); new Crontab('55 */51 * * * *', function () { @@ -87,6 +88,12 @@ class UpdateGoogleAdsTask } ); + new Crontab('0 */15 * * * *', function () { + dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsCustomers::CUSTOMERADD . '开始'); + Event::emit(GoogleAdsCustomers::CUSTOMERADD,[]); + } + ); + // 每15分钟执行一次 diff --git a/app/service/BaseService.php b/app/service/BaseService.php index e3b2794..7e7d22c 100644 --- a/app/service/BaseService.php +++ b/app/service/BaseService.php @@ -2,7 +2,7 @@ // app/service/BaseService.php namespace app\service; -use app\model\ThirdUserAdvertiser; +use app\model\ThirdUser; use think\db\exception\BindParamException; use think\facade\Db as ThinkDb; use app\model\Campaign as CampaignModel; @@ -84,19 +84,18 @@ class BaseService /** * 从数据库获取 refreshToken * - * @param string $advertiserId 广告主ID + * @param string $thirdUserId 广告主ID * @return string|null */ - public function getRefreshTokenFromDatabase($advertiserId) + public function getRefreshTokenFromDatabase($thirdUserId) { // 通过 advertiser_id 查询 ThirdUserAdvertiser,联表查询 ThirdUser 数据 - $userAdvertiser = ThirdUserAdvertiser::with('googleUser') - ->where('advertiser_id', $advertiserId) - ->find(); + $thirdUser = ThirdUser::find($thirdUserId); +//dump($thirdUser); // 如果找到广告主数据 - if ($userAdvertiser && $userAdvertiser->googleUser) { - return $userAdvertiser->googleUser->access_token; // 返回 access_token + if ($thirdUser) { + return $thirdUser->access_token; // 返回 access_token } return null; // 如果没有找到,返回 null diff --git a/app/service/GoogleAdsAccountService.php b/app/service/GoogleAdsAccountService.php index c99741f..e95d604 100644 --- a/app/service/GoogleAdsAccountService.php +++ b/app/service/GoogleAdsAccountService.php @@ -9,26 +9,36 @@ use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder; use Google\Ads\GoogleAds\Lib\V18\GoogleAdsClient; use Google\Ads\GoogleAds\Lib\V18\GoogleAdsClientBuilder; use Google\Ads\GoogleAds\Lib\V18\GoogleAdsException; +use Google\Ads\GoogleAds\Lib\V18\GoogleAdsServerStreamDecorator; use Google\Ads\GoogleAds\Util\FieldMasks; use Google\Ads\GoogleAds\Util\V18\ResourceNames; use Google\Ads\GoogleAds\V18\Enums\ManagerLinkStatusEnum\ManagerLinkStatus; use Google\Ads\GoogleAds\V18\Errors\GoogleAdsError; +use Google\Ads\GoogleAds\V18\Resources\CustomerClient; +use Google\Ads\GoogleAds\V18\Services\Client\CustomerServiceClient; +//use Google\Ads\GoogleAds\V18\Services\CustomerServiceClient; +use Google\Ads\GoogleAds\V18\Services\GoogleAdsRow; +use Google\Ads\GoogleAds\V18\Services\ListAccessibleCustomersRequest; +use Google\Ads\GoogleAds\V18\Services\SearchGoogleAdsStreamRequest; use Google\Ads\GoogleAds\V18\Resources\CustomerClientLink; use Google\Ads\GoogleAds\V18\Resources\CustomerManagerLink; use Google\Ads\GoogleAds\V18\Services\CustomerClientLinkOperation; use Google\Ads\GoogleAds\V18\Services\CustomerManagerLinkOperation; -use Google\Ads\GoogleAds\V18\Services\ListAccessibleCustomersRequest; use Google\Ads\GoogleAds\V18\Services\MutateCustomerClientLinkRequest; use Google\Ads\GoogleAds\V18\Services\MutateCustomerManagerLinkRequest; use Google\Ads\GoogleAds\V18\Services\SearchGoogleAdsRequest; use Google\ApiCore\ApiException; -class GoogleAdsAccountService +class GoogleAdsAccountService extends BaseService { // private $googleAdsClient; // private $customerId; // private const NUMBER_OF_CAMPAIGNS_TO_ADD = 1; - private $loginCustomerId; // 增加 login-customer-id 属性 +// private $loginCustomerId; // 增加 login-customer-id 属性 + + // Stores the mapping from the root customer IDs (the ones that will be used as a start point + // for printing each hierarchy) to their `CustomerClient` objects. + private static array $rootCustomerClients = []; //必须在入口初始化空数组 public function __construct() { @@ -44,36 +54,6 @@ class GoogleAdsAccountService // ->build(); } - /** - * Runs the example. - * - * @param GoogleAdsClient $googleAdsClient the Google Ads API client - */ - // [START list_accessible_customers] - public static function runListAccessibleCustomers() - { - // Creates a client with the manager customer ID as login customer ID. - $googleAdsClient = self::createGoogleAdsClient(0); - - $customerServiceClient = $googleAdsClient->getCustomerServiceClient(); - - // Issues a request for listing all accessible customers. - $accessibleCustomers = - $customerServiceClient->listAccessibleCustomers(new ListAccessibleCustomersRequest()); - print 'Total results: ' . count($accessibleCustomers->getResourceNames()) . PHP_EOL; - - $resourceNameArray = []; - // Iterates over all accessible customers' resource names and prints them. - foreach ($accessibleCustomers->getResourceNames() as $resourceName) { - /** @var string $resourceName */ - printf("Customer resource name: '%s'%s", $resourceName, PHP_EOL); - $resourceNameArray[] = $resourceName; - } - return $resourceNameArray; - } - // [END list_accessible_customers] - - /** * Runs the example. * @@ -121,14 +101,14 @@ class GoogleAdsAccountService * @param int $clientCustomerId the customer ID * @return string the resource name of the customer client link created for the invitation */ - private static function createInvitation( + public function createInvitation( int $managerCustomerId, int $clientCustomerId ) { // Creates a client with the manager customer ID as login customer ID. - $googleAdsClient = self::createGoogleAdsClient($managerCustomerId); + $googleAdsClient = $this->createGoogleAdsClient($managerCustomerId); // Creates a customer client link. $customerClientLink = new CustomerClientLink([ @@ -173,14 +153,14 @@ class GoogleAdsAccountService * @param string $customerClientLinkResourceName the customer client link resource name * @return string the manager link resource name */ - private static function getManagerLinkResourceName( + public function getManagerLinkResourceName( int $managerCustomerId, int $clientCustomerId, string $customerClientLinkResourceName ) { // Creates a client with the manager customer ID as login customer ID. - $googleAdsClient = self::createGoogleAdsClient($managerCustomerId); + $googleAdsClient = $this->createGoogleAdsClient($managerCustomerId); // Creates the query. $query = "SELECT customer_client_link.manager_link_id FROM customer_client_link" . @@ -221,13 +201,13 @@ class GoogleAdsAccountService * @param int $clientCustomerId the customer ID * @param string $managerLinkResourceName the resource name of the manager link to accept */ - private static function acceptInvitation( + public function acceptInvitation( int $clientCustomerId, string $managerLinkResourceName ) { // Creates a client with the client customer ID as login customer ID. - $googleAdsClient = self::createGoogleAdsClient($clientCustomerId); + $googleAdsClient = $this->createGoogleAdsClient($clientCustomerId); // Creates the customer manager link with the updated status. $customerManagerLink = new CustomerManagerLink(); @@ -266,18 +246,18 @@ class GoogleAdsAccountService * Creates a Google Ads client based on the default configuration file * and a given login customer id. * - * @param int $loginCustomerId the login customer ID + * @param int $thirdUserId thirdUser * @return GoogleAdsClient the created client */ - private static function createGoogleAdsClient(int $loginCustomerId) +// public function createGoogleAdsClient(int $thirdUserId) + public function createGoogleAdsClient($refreshToken) { - $advertiserId = getenv('GOOGLE_ADS_CUSTOMER_ID'); // 从数据库获取 access_token - $refreshToken = self::getRefreshTokenFromDatabase($advertiserId); +// $refreshToken = $this->getRefreshTokenFromDatabase($thirdUserId); if (!$refreshToken) { - throw new \Exception("Access token not found for advertiserId: " . $advertiserId); + throw new \Exception("Access token not found for advertiserId: " . $refreshToken); } // OAuth2 Token Authentication @@ -286,46 +266,351 @@ class GoogleAdsAccountService ->withRefreshToken($refreshToken) // 使用动态获取的 access_token ->build(); - if ($loginCustomerId > 0) { - // Builds and returns the Google Ads client - return ( - new GoogleAdsClientBuilder()) - // Sets the properties based on the default properties file - ->fromFile() - // eUses the OAuth2 credentials crated above. - ->withOAuth2Credential($oAuth2Credential) - // Overrides the login customer ID with the given one. - ->withLoginCustomerId($loginCustomerId) - ->build(); + + // Builds and returns the Google Ads client + return (new GoogleAdsClientBuilder()) + // Sets the properties based on the default properties file + ->fromFile() + // eUses the OAuth2 credentials crated above. + ->withOAuth2Credential($oAuth2Credential) + // Overrides the login customer ID with the given one. + ->build(); + + + } + + /** + * Creates a Google Ads client based on the default configuration file + * and a given login customer id. + * + * @param int $loginCustomerId the login customer ID + * @return GoogleAdsClient the created client + */ +// public function createGoogleAdsClientWithloginCustomerId(int $thirdUserId,int $loginCustomerId) + public function createGoogleAdsClientWithloginCustomerId($refreshToken,int $loginCustomerId) + { +// $advertiserId = getenv('GOOGLE_ADS_CUSTOMER_ID'); + + // 从数据库获取 access_token +// $refreshToken = $this->getRefreshTokenFromDatabase($thirdUserId); + + if (!$refreshToken) { + throw new \Exception("Access token not found for advertiserId: " . $refreshToken); + } + // OAuth2 Token Authentication + $oAuth2Credential = (new OAuth2TokenBuilder()) + ->fromFile() // 如果需要从文件获取其他配置,可以继续使用 fromFile() + ->withRefreshToken($refreshToken) // 使用动态获取的 access_token + ->build(); + + // Builds and returns the Google Ads client + return ( + new GoogleAdsClientBuilder()) + // Sets the properties based on the default properties file + ->fromFile() + // eUses the OAuth2 credentials crated above. + ->withOAuth2Credential($oAuth2Credential) + // Overrides the login customer ID with the given one. + ->withLoginCustomerId($loginCustomerId) + ->build(); + } + + /** + * Runs the example. + * + * @param GoogleAdsClient $googleAdsClient the Google Ads API client + */ + // [START list_accessible_customers] + public function runListAccessibleCustomers($option) + { +// $loginCustomerId = $option['customer_id']; + $refreshToken = $option['refresh_token']; + // Creates a client with the manager customer ID as login customer ID. + $googleAdsClient = $this->createGoogleAdsClient($refreshToken); + + $customerServiceClient = $googleAdsClient->getCustomerServiceClient(); + + // Issues a request for listing all accessible customers. + $accessibleCustomers = + $customerServiceClient->listAccessibleCustomers(new ListAccessibleCustomersRequest()); + print 'Total results: ' . count($accessibleCustomers->getResourceNames()) . PHP_EOL; + + $resourceNameArray = []; + // Iterates over all accessible customers' resource names and prints them. + foreach ($accessibleCustomers->getResourceNames() as $resourceName) { + /** @var string $resourceName */ + printf("Customer resource name: '%s'%s", $resourceName, PHP_EOL); + $resourceNameArray[] = $resourceName; + } + return $resourceNameArray; + } + // [END list_accessible_customers] + + + /** + * 层级账号信息 + * + */ + public function runGetAccountHierarchy( + $option + ) + { + + self::$rootCustomerClients = []; //初始化静态变量 + + $managerCustomerId = isset($option['manager_customer_id'])?(int)$option['manager_customer_id']: null; + $loginCustomerId = isset($option['login_customer_id'])?(int)$option['login_customer_id']: null; + $refreshToken = isset($option['refresh_token'])?$option['refresh_token']: null; +// $thirdUserId = (int)$option['third_user_id']; + + // Creates a client with the manager customer ID as login customer ID. + $googleAdsClient = $this->createGoogleAdsClient($refreshToken); + +// $customerServiceClient = $googleAdsClient->getCustomerServiceClient(); + + + $rootCustomerIds = []; + if (is_null($managerCustomerId)) { + // We will get the account hierarchies for all accessible customers. + $rootCustomerIds = $this->getAccessibleCustomers($googleAdsClient); } else { - // Builds and returns the Google Ads client - return (new GoogleAdsClientBuilder()) - // Sets the properties based on the default properties file - ->fromFile() - // eUses the OAuth2 credentials crated above. - ->withOAuth2Credential($oAuth2Credential) - // Overrides the login customer ID with the given one. - ->build(); + // We will get only the hierarchy for the provided manager customer ID when it's + // provided. + $rootCustomerIds[] = $managerCustomerId; } + $allHierarchies = []; + $accountsWithNoInfo = []; + // Constructs a map of account hierarchies. + foreach ($rootCustomerIds as $rootCustomerId) { + $customerClientToHierarchy = + $this->createCustomerClientToHierarchy($refreshToken,$loginCustomerId, $rootCustomerId); + if (is_null($customerClientToHierarchy)) { + $accountsWithNoInfo[] = $rootCustomerId; + } else { + $allHierarchies += $customerClientToHierarchy; + } + } + + // Prints the IDs of any accounts that did not produce hierarchy information. + if (!empty($accountsWithNoInfo)) { + print + 'Unable to retrieve information for the following accounts which are likely ' + . 'either test accounts or accounts with setup issues. Please check the logs for ' + . 'details:' . PHP_EOL; + foreach ($accountsWithNoInfo as $accountId) { + print $accountId . PHP_EOL; + } + print PHP_EOL; + } + $allAccounts = []; + // Prints the hierarchy information for all accounts for which there is hierarchy info + // available. + foreach ($allHierarchies as $rootCustomerId => $customerIdsToChildAccounts) { +// printf( +// "The hierarchy of customer ID %d is printed below:%s", +// $rootCustomerId, +// PHP_EOL +// ); + // 获取所有账户的数组(包括根账户及其子账户) + $accounts = []; + self::getAccountHierarchy( + self::$rootCustomerClients[$rootCustomerId], + $customerIdsToChildAccounts, + 0, + $accounts); + $allAccounts[$rootCustomerId] = $accounts; + +// self::printAccountHierarchy( +// self::$rootCustomerClients[$rootCustomerId], +// $customerIdsToChildAccounts, +// 0 +// ); +// print PHP_EOL; + } +// dump($allAccounts); + return $allAccounts; } - - // 从数据库动态获取 google RefreshToken - private function getRefreshTokenFromDatabase($advertiserId) + /** + * Creates a map between a customer client and each of its managers' mappings. + * + * @param int|null $loginCustomerId the login customer ID used to create the GoogleAdsClient + * @param int $rootCustomerId the ID of the customer at the root of the tree + * @return array|null a map between a customer client and each of its managers' mappings if the + * account hierarchy can be retrieved. If the account hierarchy cannot be retrieved, returns + * null + */ + public function createCustomerClientToHierarchy( + string $refreshToken, + ?int $loginCustomerId, + int $rootCustomerId + ): ?array { - // 使用 ThinkDb 进行联表查询 -// $advertiserId = 'your-advertiser-id'; // 假设你已经获得了广告商ID - $user = ThinkDb::table('bps.bps_third_user_advertiser as a') - ->join('bps_third_user as u', 'a.doc_ = u.id', 'left') // 连接 bps_third_user 表 - ->where('a.advertiser_id', $advertiserId) - ->select('u.access_token') // 只选择 access_token 字段 - ->first(); - return $user ? $user->access_token : null; + // Creates a client with the manager customer ID as login customer ID. + $googleAdsClient = $this->createGoogleAdsClientWithloginCustomerId($refreshToken,$loginCustomerId ?? $rootCustomerId); + + // Creates the Google Ads Service client. + $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); + // Creates a query that retrieves all child accounts of the manager specified in search + // calls below. + $query = 'SELECT customer_client.client_customer, customer_client.level,' + . ' customer_client.manager, customer_client.descriptive_name,' + . ' customer_client.currency_code, customer_client.time_zone,' + . ' customer_client.id FROM customer_client WHERE customer_client.level <= 1'; + + $rootCustomerClient = null; + // Adds the root customer ID to the list of IDs to be processed. + $managerCustomerIdsToSearch = [$rootCustomerId]; + + // Performs a breadth-first search algorithm to build an associative array mapping + // managers to their child accounts ($customerIdsToChildAccounts). + $customerIdsToChildAccounts = []; + + while (!empty($managerCustomerIdsToSearch)) { + $customerIdToSearch = array_shift($managerCustomerIdsToSearch); + // Issues a search request. + /** @var GoogleAdsServerStreamDecorator $stream */ + $stream = $googleAdsServiceClient->searchStream(SearchGoogleAdsStreamRequest::build( + $customerIdToSearch, + $query + )); + + // Iterates over all elements to get all customer clients under the specified customer's + // hierarchy. + foreach ($stream->iterateAllElements() as $googleAdsRow) { + /** @var GoogleAdsRow $googleAdsRow */ + $customerClient = $googleAdsRow->getCustomerClient(); + + // Gets the CustomerClient object for the root customer in the tree. + if ($customerClient->getId() === $rootCustomerId) { + $rootCustomerClient = $customerClient; + self::$rootCustomerClients[$rootCustomerId] = $rootCustomerClient; + } + + // The steps below map parent and children accounts. Continue here so that managers + // accounts exclude themselves from the list of their children accounts. + if ($customerClient->getId() === $customerIdToSearch) { + continue; + } + + // For all level-1 (direct child) accounts that are a manager account, the above + // query will be run against them to create an associative array of managers to + // their child accounts for printing the hierarchy afterwards. + $customerIdsToChildAccounts[$customerIdToSearch][] = $customerClient; + // Checks if the child account is a manager itself so that it can later be processed + // and added to the map if it hasn't been already. + if ($customerClient->getManager()) { + // A customer can be managed by multiple managers, so to prevent visiting + // the same customer multiple times, we need to check if it's already in the + // map. + $alreadyVisited = array_key_exists( + $customerClient->getId(), + $customerIdsToChildAccounts + ); + if (!$alreadyVisited && $customerClient->getLevel() === 1) { + array_push($managerCustomerIdsToSearch, $customerClient->getId()); + } + } + } + } + + return is_null($rootCustomerClient) ? null + : [$rootCustomerClient->getId() => $customerIdsToChildAccounts]; } + /** + * Retrieves a list of accessible customers with the provided set up credentials. + * + * @param GoogleAdsClient $googleAdsClient the Google Ads API client + * @return int[] the list of customer IDs + */ + public function getAccessibleCustomers(GoogleAdsClient $googleAdsClient): array + { + $accessibleCustomerIds = []; + // Issues a request for listing all customers accessible by this authenticated Google + // account. + $customerServiceClient = $googleAdsClient->getCustomerServiceClient(); + $accessibleCustomers = + $customerServiceClient->listAccessibleCustomers(new ListAccessibleCustomersRequest()); + + print 'No manager customer ID is specified. The example will print the hierarchies of' + . ' all accessible customer IDs:' . PHP_EOL; + foreach ($accessibleCustomers->getResourceNames() as $customerResourceName) { + $customer = CustomerServiceClient::parseName($customerResourceName)['customer_id']; + print $customer . PHP_EOL; + $accessibleCustomerIds[] = intval($customer); + } + print PHP_EOL; + + return $accessibleCustomerIds; + } + + /** + * Prints the specified account's hierarchy using recursion. + * + * @param CustomerClient $customerClient the customer client whose info will be printed and + * its child accounts will be processed if it's a manager + * @param array $customerIdsToChildAccounts a map from customer IDs to child + * accounts + * @param int $depth the current depth we are printing from in the + * account hierarchy + */ + private static function printAccountHierarchy( + CustomerClient $customerClient, + array $customerIdsToChildAccounts, + int $depth + ) + { + if ($depth === 0) { + print 'Customer ID (Descriptive Name, Currency Code, Time Zone)' . PHP_EOL; + } + $customerId = $customerClient->getId(); + print str_repeat('-', $depth * 2); + printf( + " %d ('%s', '%s', '%s')%s", + $customerId, + $customerClient->getDescriptiveName(), + $customerClient->getCurrencyCode(), + $customerClient->getTimeZone(), + PHP_EOL + ); + + // Recursively call this function for all child accounts of $customerClient. + if (array_key_exists($customerId, $customerIdsToChildAccounts)) { + foreach ($customerIdsToChildAccounts[$customerId] as $childAccount) { + self::printAccountHierarchy($childAccount, $customerIdsToChildAccounts, $depth + 1); + } + } + } + + private static function getAccountHierarchy( + CustomerClient $customerClient, + array $customerIdsToChildAccounts, + int $depth, + array &$result = [] // 这里使用引用传递 +) +{ + // Store the current customer ID and descriptive name in the result array + $customerId = $customerClient->getId(); + $result[] = [ + 'customer_id' => $customerId, + 'descriptive_name' => $customerClient->getDescriptiveName(), + ]; + + // Recursively call this function for all child accounts of $customerClient. + if (array_key_exists($customerId, $customerIdsToChildAccounts)) { + foreach ($customerIdsToChildAccounts[$customerId] as $childAccount) { + // Recursively add the child account information + $result = self::getAccountHierarchy($childAccount, $customerIdsToChildAccounts, $depth + 1, $result); + } + } + + return $result; +} + } diff --git a/app/service/GoogleAdsAdService.php b/app/service/GoogleAdsAdService.php index e0cde38..c215fcb 100644 --- a/app/service/GoogleAdsAdService.php +++ b/app/service/GoogleAdsAdService.php @@ -70,14 +70,14 @@ class GoogleAdsAdService extends BaseService * @return mixed * @throws ApiException */ - public function runListAds(int $customerId): mixed + public function runListAds(int $customerId,$options): mixed { // $googleAdsClient = $this->googleAdsClient; - $googleAdsClient = new GoogleAdsClientService($customerId); + $googleAdsClient = new GoogleAdsClientService($options['refresh_token'], $options['login_customer_id']); // Creates a single shared budget to be used by the campaigns added below. - $groupAdsResourceName = self::getAds($googleAdsClient->getGoogleAdsClient(), $customerId); + $groupAdsResourceName = self::getAds($googleAdsClient->getGoogleAdsClientWithloginCustomerId(), $customerId); // dump(json_encode($groupAdsResourceName)); if (is_array($groupAdsResourceName)) { self::saveAds($groupAdsResourceName); diff --git a/app/service/GoogleAdsAssetRelationService.php b/app/service/GoogleAdsAssetRelationService.php index fcd9bd4..2958081 100644 --- a/app/service/GoogleAdsAssetRelationService.php +++ b/app/service/GoogleAdsAssetRelationService.php @@ -33,6 +33,7 @@ use app\util\ArgumentParser; //use Google\Protobuf\Internal\RepeatedField; +use DateTime; use think\facade\Db as ThinkDb; use app\model\Ad as AdModel; use app\model\Asset as AssetModel; @@ -76,10 +77,11 @@ class GoogleAdsAssetRelationService extends BaseService */ public function runListAssetRelations(int $customerId): mixed { +// dump($customerId); // Creates a single shared budget to be used by the campaigns added below. $assetsResourceName = self::getAssetRelations($customerId); // dump(json_encode($assetsResourceName)); - if (is_array($assetsResourceName)) { + if (is_array($assetsResourceName && count($assetsResourceName) > 0)) { self::saveAssetRelations($assetsResourceName); } // return $assetsResourceName; @@ -142,12 +144,18 @@ class GoogleAdsAssetRelationService extends BaseService $month = $dateDetails['month']; $season = $dateDetails['season']; +// dump($year, $month, $season); + // 获取所有素材 // $assets = AssetModel::where('asset_type', 4)->select(); //图片素材 - $resourceNames = AssetModel::where('asset_type', 4) + $resourceNames = AssetModel::where('asset_type', 4)->where('customer_id', $customerId) ->column('asset_id, resource_name'); + if (!$resourceNames) { + return []; + } + // dump($resourceNames);return($resourceNames); $result = []; foreach ($resourceNames as $resourceName) { @@ -240,17 +248,17 @@ class GoogleAdsAssetRelationService extends BaseService // 提取年和月 $year = (int)$dateObj->format('Y'); - $month = (int)$dateObj->format('m'); + $month = (int)$dateObj->format('Ym'); // 计算季度 if ($month >= 1 && $month <= 3) { - $season = (int)$dateObj->format('Ym') . '01'; // Q1 + $season = (int)$dateObj->format('Y') . '01'; // Q1 } elseif ($month >= 4 && $month <= 6) { - $season = (int)$dateObj->format('Ym') . '02'; // Q2 + $season = (int)$dateObj->format('Y') . '02'; // Q2 } elseif ($month >= 7 && $month <= 9) { - $season = (int)$dateObj->format('Ym') . '03'; // Q3 + $season = (int)$dateObj->format('Y') . '03'; // Q3 } else { - $season = (int)$dateObj->format('Ym') . '04'; // Q4 + $season = (int)$dateObj->format('Y') . '04'; // Q4 } return [ @@ -259,476 +267,4 @@ class GoogleAdsAssetRelationService extends BaseService 'season' => $season ]; } - - - /** - * This example updates the CPC bid and status for a given ad group. To get ad groups, run - * GetAdAds.php. - */ - /* @param int $customerId the customer ID - * @param $options - * @return mixed - * @throws ApiException - */ - public function runUpdateAd($options): mixed - { -// $googleAdsClient = $this->googleAdsClient; - $googleAdsClient = new GoogleAdsClientService($options['customer_id']); - // Creates a single shared budget to be used by the campaigns added below. - - $resourceNames = self::updateAd($googleAdsClient->getGoogleAdsClient(), $options['customer_id'], $options['group_id'], $options['ad_id'], $options['status']); - - return $resourceNames; - } - - - /** - * Runs the updateAd example. - * - * @param GoogleAdsClient $googleAdsClient the Google Ads API client - * @param int $customerId the customer ID - * @param int $adGroupId the ad group ID that the ad group ad belongs to - * @param int $adId the ID of the ad to pause - */ - public static function updateAd( - GoogleAdsClient $googleAdsClient, - int $customerId, - int $adGroupId, - int $adId, - int $status - ) - { - // Creates ad group ad resource name. - $adGroupAdResourceName = ResourceNames::forAdGroupAd($customerId, $adGroupId, $adId); - - // Creates an ad and sets its status to PAUSED. - $adGroupAd = new AdGroupAd(); - $adGroupAd->setResourceName($adGroupAdResourceName); -// $adGroupAd->setStatus(AdGroupAdStatus::PAUSED); - $adGroupAd->setStatus($status); - - // Constructs an operation that will pause the ad with the specified resource name, - // using the FieldMasks utility to derive the update mask. This mask tells the Google Ads - // API which attributes of the ad group you want to change. - $adGroupAdOperation = new AdGroupAdOperation(); - $adGroupAdOperation->setUpdate($adGroupAd); - $adGroupAdOperation->setUpdateMask(FieldMasks::allSetFieldsOf($adGroupAd)); - - // Issues a mutate request to pause the ad group ad. - $adGroupAdServiceClient = $googleAdsClient->getAdGroupAdServiceClient(); - $response = $adGroupAdServiceClient->mutateAdGroupAds(MutateAdGroupAdsRequest::build( - $customerId, - [$adGroupAdOperation] - )); - - // Prints the resource name of the paused ad group ad. - /** @var AdGroupAd $pausedAdGroupAd */ - $pausedAdGroupAd = $response->getResults()[0]; -// printf( -// "Ad group ad with resource name: '%s' is paused.%s", -// $pausedAdGroupAd->getResourceName(), -// PHP_EOL -// ); - return $pausedAdGroupAd->getResourceName(); - } - - /** - * 更新广告状态 - */ - public function updateAdStatus(int $customerId, int $adGroupId, int $adId, int $status) - { - // 从数据库获取 Ad - $ad = AdModel::find($adId); - if (!$ad) { -// throw new ValidateException('Ad not found'); - return false; - } - // 更新数据库中的状态 -// $ad->updateStatus($status); - if ($this->modifyDbAdStatus($adId, $status)) { - // 更新 Google Ads 上的状态 -// $googleAdsClient = $this->googleAdsClient; - $googleAdsClient = new GoogleAdsClientService($customerId); - $resourceName = self::updateAd($googleAdsClient->getGoogleAdsClient(), $customerId, $adGroupId, $adId, $status); - return true; - } - - return false; - } - - /** - * 获取广告状态 - */ - public function getAdStatus(int $adId) - { - // 从数据库获取 Ad - $ad = AdModel::find($adId); - if (!$ad) { -// throw new ValidateException('Ad not found'); - } - - // 返回广告状态 - return $ad->getStatusTextAttr(null, $ad->toArray()); - } - - - - /** - * 判断广告是否启用 - */ -// public function isAdEnabled(int $adId) -// { -// $ad = Ad::find($adId); -// if (!$ad) { -// throw new ValidateException('Ad not found'); -// } -// -// return $ad->isEnabled(); -// } - - /** - * 判断广告是否暂停 - */ -// public function isAdPaused(int $adId) -// { -// $ad = Ad::find($adId); -// if (!$ad) { -// throw new ValidateException('Ad not found'); -// } -// -// return $ad->isPaused(); -// } - - /** - * 判断广告是否停止 - */ -// public function isAdStopped(int $adId) -// { -// $ad = Ad::find($adId); -// if (!$ad) { -// throw new ValidateException('Ad not found'); -// } -// -// return $ad->isStopped(); -// } - - - /** - * This example updates the CPC bid and status for a given ad group. To get ad groups, run - * GetAdAds.php. - */ - /* @param int $customerId the customer ID - * @param $options - * @return mixed - * @throws ApiException - */ - public function runGetResponsiveSearchAds($options): mixed - { -// $googleAdsClient = $this->googleAdsClient; - $googleAdsClient = new GoogleAdsClientService($options['customer_id']); - // Creates a single shared budget to be used by the campaigns added below. - if (!isset($options['group_id'])) { - $options['group_id'] = null; - } - $resourceNames = self::getResponsiveSearchAds($googleAdsClient->getGoogleAdsClient(), $options['customer_id'], $options['group_id']); - - return $resourceNames; - } - - /** - * 获取指定广告组中未移除的自适应搜索广告。 - * - * @param GoogleAdsClient $googleAdsClient the Google Ads API client - * @param int $customerId the customer ID - * @param int|null $adGroupId the ad group ID for which responsive search ads will be retrieved. - * If `null`, returns from all ad groups - */ - public static function getResponsiveSearchAds( - GoogleAdsClient $googleAdsClient, - int $customerId, - ?int $adGroupId - ) - { - $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); - - // Creates a query that retrieves responsive search ads. - $query = - 'SELECT ad_group.id, ' - . 'ad_group_ad.ad.id, ' - . 'ad_group_ad.ad.responsive_search_ad.headlines, ' - . 'ad_group_ad.ad.responsive_search_ad.descriptions, ' - . 'ad_group_ad.status ' - . 'FROM ad_group_ad ' - . 'WHERE ad_group_ad.ad.type = RESPONSIVE_SEARCH_AD ' - . 'AND ad_group_ad.status != "REMOVED"'; - if (!is_null($adGroupId)) { - $query .= " AND ad_group.id = $adGroupId"; - } - - // Issues a search request. - $response = - $googleAdsServiceClient->search(SearchGoogleAdsRequest::build($customerId, $query)); - - // Iterates over all rows in all pages and prints the requested field values for - // the responsive search ad in each row. - $isEmptyResult = true; - $resources = []; - foreach ($response->iterateAllElements() as $googleAdsRow) { - /** @var GoogleAdsRow $googleAdsRow */ - $isEmptyResult = false; - $ad = $googleAdsRow->getAdGroupAd()->getAd(); - $resource = []; -// printf( -// "Responsive search ad with resource name '%s' and status '%s' was found.%s", -// $ad->getResourceName(), -// AdGroupAdStatus::name($googleAdsRow->getAdGroupAd()->getStatus()), -// PHP_EOL -// ); - $resource['resource_name'] = $ad->getResourceName(); - $resource['status'] = AdGroupAdStatus::name($googleAdsRow->getAdGroupAd()->getStatus()); - $responsiveSearchAdInfo = $ad->getResponsiveSearchAd(); -// printf( -// 'Headlines:%1$s%2$sDescriptions:%1$s%3$s%1$s', -// PHP_EOL, -// self::convertAdTextAssetsToString($responsiveSearchAdInfo->getHeadlines()), -// self::convertAdTextAssetsToString($responsiveSearchAdInfo->getDescriptions()) -// ) - $resource['content'] = sprintf( - 'Headlines:%1$s%2$sDescriptions:%1$s%3$s%1$s', - PHP_EOL, - self::convertAdTextAssetsToString($responsiveSearchAdInfo->getHeadlines()), - self::convertAdTextAssetsToString($responsiveSearchAdInfo->getDescriptions()) - ); - $resources[] = $resource; - } - return $resources; -// if ($isEmptyResult) { -// print 'No responsive search ads were found.' . PHP_EOL; -// } - } - - /** - * Converts the list of AdTextAsset objects into a string representation. - * - * @param RepeatedField $assets the list of AdTextAsset objects - * @return string the string representation of the provided list of AdTextAsset objects - */ - private static function convertAdTextAssetsToString(RepeatedField $assets): string - { - $result = ''; - foreach ($assets as $asset) { - /** @var AdTextAsset $asset */ - $result .= sprintf( - "\t%s pinned to %s.%s", - $asset->getText(), - ServedAssetFieldType::name($asset->getPinnedField()), - PHP_EOL - ); - } - return $result; - } - - /** - * This example updates the CPC bid and status for a given ad group. To get ad groups, run - * GetAdAds.php. - */ - /* @param int $customerId the customer ID - * @param $options - * @return mixed - * @throws ApiException - */ - public function runUpdateResponsiveSearchAd($options): mixed - { -// $googleAdsClient = $this->googleAdsClient; - $googleAdsClient = new GoogleAdsClientService($options['customer_id']); - // Creates a single shared budget to be used by the campaigns added below. - $resourceName = self::updateResponsiveSearchAd($googleAdsClient->getGoogleAdsClient(), $options['customer_id'], $options['ad_id']); - - return $resourceName; - } - - /** - * updateResponsiveSearchAd. - * - * @param GoogleAdsClient $googleAdsClient the Google Ads API client - * @param int $customerId the customer ID - * @param int $adId the ad ID to update - */ - // [START update_responsive_search_ad] - public static function updateResponsiveSearchAd( - GoogleAdsClient $googleAdsClient, - int $customerId, - int $adId - ) - { - // Creates an ad with the specified resource name and other changes. - $ad = new Ad([ - 'resource_name' => ResourceNames::forAd($customerId, $adId), - 'responsive_search_ad' => new ResponsiveSearchAdInfo([ - // Update some properties of the responsive search ad. - 'headlines' => [ - new AdTextAsset([ - 'text' => 'Cruise to Pluto #' . Helper::getShortPrintableDatetime(), - 'pinned_field' => ServedAssetFieldType::HEADLINE_1 - ]), - new AdTextAsset(['text' => 'Tickets on sale now', 'pinned_field' => ServedAssetFieldType::HEADLINE_2]), - new AdTextAsset(['text' => 'Buy your ticket now']) - ], - 'descriptions' => [ - new AdTextAsset(['text' => 'Best space cruise ever.']), - new AdTextAsset([ - 'text' => 'The most wonderful space experience you will ever have.']) - ] - ]), - 'final_urls' => ['http://www.baidu.com'], - 'final_mobile_urls' => ['http://www.baidu.com/mobile'] - ]); - - // Constructs an operation that will update the ad, using the FieldMasks to derive the - // update mask. This mask tells the Google Ads API which attributes of the ad you want to - // change. - $adOperation = new AdOperation(); - $adOperation->setUpdate($ad); - $adOperation->setUpdateMask(FieldMasks::allSetFieldsOf($ad)); - - // Issues a mutate request to update the ad. - $adServiceClient = $googleAdsClient->getAdServiceClient(); - $response = - $adServiceClient->mutateAds(MutateAdsRequest::build($customerId, [$adOperation])); - - // Prints the resource name of the updated ad. - /** @var Ad $updatedAd */ - $updatedAd = $response->getResults()[0]; - printf( - "Updated ad with resource name: '%s'.%s", - $updatedAd->getResourceName(), - PHP_EOL - ); - return $updatedAd->getResourceName(); - } - // [END update_responsive_search_ad] - - - /** - * 根据给定的前缀搜索 Google Ads 字段,检索到关于这些字段的元数据(metadata) - */ - /* @param int $customerId the customer ID - * @param $options - * @return mixed - * @throws ApiException - */ - public function runSearchForGoogleAdsFields($options): mixed - { -// $googleAdsClient = $this->googleAdsClient; - $googleAdsClient = new GoogleAdsClientService($options['customer_id']); - // Creates a single shared budget to be used by the campaigns added below. - $googleAdsFieldData = self::searchForGoogleAdsFields($googleAdsClient->getGoogleAdsClient(), $options['name_prefix']); - - return $googleAdsFieldData; - } - - /** - * Runs the example SearchForGoogleAdsFields. - * - * @param GoogleAdsClient $googleAdsClient the Google Ads API client - * @param string $namePrefix the name prefix to use in the query - */ - public static function searchForGoogleAdsFields(GoogleAdsClient $googleAdsClient, string $namePrefix) - { - $googleAdsFieldServiceClient = $googleAdsClient->getGoogleAdsFieldServiceClient(); - // Searches for all fields whose name begins with the specified namePrefix. - // A single "%" is the wildcard token in the Google Ads Query language. - $query = "SELECT name, category, selectable, filterable, sortable, selectable_with, " - . "data_type, is_repeated WHERE name LIKE '$namePrefix%'"; - $response = $googleAdsFieldServiceClient->searchGoogleAdsFields( - SearchGoogleAdsFieldsRequest::build($query) - ); - - if (iterator_count($response->getIterator()) === 0) { - printf( - "No GoogleAdsFields found with a name that begins with %s.%s", - $namePrefix, - PHP_EOL - ); - return; - } - // Iterates over all rows and prints our the metadata of each matching GoogleAdsField. - foreach ($response->iterateAllElements() as $googleAdsField) { - /** @var GoogleAdsField $googleAdsField */ - $fieldInfo = [ - 'name' => $googleAdsField->getName(), - 'category' => GoogleAdsFieldCategory::name($googleAdsField->getCategory()), - 'data_type' => GoogleAdsFieldDataType::name($googleAdsField->getDataType()), - 'selectable' => $googleAdsField->getSelectable() ? 'true' : 'false', - 'filterable' => $googleAdsField->getFilterable() ? 'true' : 'false', - 'sortable' => $googleAdsField->getSortable() ? 'true' : 'false', - 'repeated' => $googleAdsField->getIsRepeated() ? 'true' : 'false', - 'selectable_with' => [] - ]; - // Check if there are fields that are selectable with the current field - if ($googleAdsField->getSelectableWith()->count() > 0) { - $selectableWithFields = iterator_to_array($googleAdsField->getSelectableWith()->getIterator()); - sort($selectableWithFields); // Sort the fields alphabetically - $fieldInfo['selectable_with'] = $selectableWithFields; - } - - // Add the field info to the result array - $googleAdsFieldData[] = $fieldInfo; -// -// printf("%s:%s", $googleAdsField->getName(), PHP_EOL); -// printf( -// " %-16s: %s%s", -// "category:", -// GoogleAdsFieldCategory::name($googleAdsField->getCategory()), -// PHP_EOL -// ); -// printf( -// " %-16s: %s%s", -// "data type:", -// GoogleAdsFieldDataType::name($googleAdsField->getDataType()), -// PHP_EOL -// ); -// printf( -// " %-16s: %s%s", -// "selectable:", -// $googleAdsField->getSelectable() ? 'true' : 'false', -// PHP_EOL -// ); -// printf( -// " %-16s: %s%s", -// "filterable:", -// $googleAdsField->getFilterable() ? 'true' : 'false', -// PHP_EOL -// ); -// printf( -// " %-16s: %s%s", -// "sortable:", -// $googleAdsField->getSortable() ? 'true' : 'false', -// PHP_EOL -// ); -// printf( -// " %-16s: %s%s", -// "repeated:", -// $googleAdsField->getIsRepeated() ? 'true' : 'false', -// PHP_EOL -// ); -// -// if ($googleAdsField->getSelectableWith()->count() > 0) { -// // Prints the list of fields that are selectable with the field. -// $selectableWithFields = -// iterator_to_array($googleAdsField->getSelectableWith()->getIterator()); -// // Sorts and then prints the list. -// sort($selectableWithFields); -// print ' selectable with:' . PHP_EOL; -// foreach ($selectableWithFields as $selectableWithField) { -// /** @var string $selectableWithField */ -// printf(" $selectableWithField%s", PHP_EOL); -// } -// } - } - - return $googleAdsFieldData; // Return the result array - } - - } diff --git a/app/service/GoogleAdsAssetService.php b/app/service/GoogleAdsAssetService.php index f2d699e..d5068e1 100644 --- a/app/service/GoogleAdsAssetService.php +++ b/app/service/GoogleAdsAssetService.php @@ -70,12 +70,12 @@ class GoogleAdsAssetService extends BaseService * @return mixed * @throws ApiException */ - public function runListAssets(int $customerId): mixed + public function runListAssets(int $customerId,$options): mixed { - $googleAdsClient = new GoogleAdsClientService($customerId); + $googleAdsClient = new GoogleAdsClientService($options['refresh_token'], $options['login_customer_id']); // Creates a single shared budget to be used by the campaigns added below. - $assetsResourceName = self::getAssets($googleAdsClient->getGoogleAdsClient(), $customerId); + $assetsResourceName = self::getAssets($googleAdsClient->getGoogleAdsClientWithloginCustomerId(), $customerId); // dump(json_encode($assetsResourceName)); if (is_array($assetsResourceName)) { self::saveAssets($assetsResourceName); diff --git a/app/service/GoogleAdsCampaignService.php b/app/service/GoogleAdsCampaignService.php index d4a5f93..137ea94 100644 --- a/app/service/GoogleAdsCampaignService.php +++ b/app/service/GoogleAdsCampaignService.php @@ -40,7 +40,7 @@ class GoogleAdsCampaignService extends BaseService private $customerId; private const NUMBER_OF_CAMPAIGNS_TO_ADD = 1; - public function __construct($customerId = null,GoogleAdsClientService $googleAdsClientService = null) + public function __construct($customerId = null, GoogleAdsClientService $googleAdsClientService = null) { // 使用 GoogleAdsClientService 来初始化 googleAdsClient // $googleAdsClient = new GoogleAdsClientService($customerId); @@ -75,7 +75,7 @@ class GoogleAdsCampaignService extends BaseService */ public function runAddCampaign(int $customerId, $options): mixed { - $googleAdsClient = new GoogleAdsClientService($customerId); + $googleAdsClient = new GoogleAdsClientService($options['refresh_token']); // Creates a single shared budget to be used by the campaigns added below. $budgetResourceName = self::addCampaignBudget($googleAdsClient->getGoogleAdsClient(), $customerId, $options); @@ -199,13 +199,11 @@ class GoogleAdsCampaignService extends BaseService * @return mixed * @throws ApiException */ - public function runListCampaigns(int $customerId): mixed + public function runListCampaigns(int $customerId, $options): mixed { -// $googleAdsClient = $this->googleAdsClient; - $googleAdsClient = new GoogleAdsClientService($customerId); - // Creates a single shared budget to be used by the campaigns added below. - $campaignsResourceName = self::getCampaigns($googleAdsClient->getGoogleAdsClient(), $customerId); + $googleAdsClient = new GoogleAdsClientService($options['refresh_token'], $options['login_customer_id']); + $campaignsResourceName = self::getCampaigns($googleAdsClient->getGoogleAdsClientWithloginCustomerId(), $customerId); // dump(json_encode($campaignsResourceName)); if (is_array($campaignsResourceName)) { self::saveCampaigns($campaignsResourceName); @@ -304,12 +302,12 @@ class GoogleAdsCampaignService extends BaseService public static function getDateDatas(GoogleAdsClient $googleAdsClient, int $customerId, $date = '2024-12-19') { - // 调用私有方法提取 year, month, season - $dateDetails = self::extractDateDetails($date); + // 调用私有方法提取 year, month, season + $dateDetails = self::extractDateDetails($date); - $year = $dateDetails['year']; - $month = $dateDetails['month']; - $season = $dateDetails['season']; + $year = $dateDetails['year']; + $month = $dateDetails['month']; + $season = $dateDetails['season']; $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); @@ -339,12 +337,11 @@ class GoogleAdsCampaignService extends BaseService $resourceName['conversions_value'] = $googleAdsRow->getMetrics()->getConversionsValue(); $resourceName['impressions'] = $googleAdsRow->getMetrics()->getImpressions(); $resourceName['date'] = $date; - $resourceName['month'] = $month; - $resourceName['season'] = $season; + $resourceName['month'] = $month; + $resourceName['season'] = $season; $resourceName['year'] = $year; - // $resourceName['budget_id'] = $googleAdsRow->getCampaignBudget()->getId(); $resourceNames[] = $resourceName; } @@ -363,18 +360,18 @@ class GoogleAdsCampaignService extends BaseService $dateObj = new DateTime($date); // 将日期字符串转换为 DateTime 对象 // 提取年和月 - $year = (int) $dateObj->format('Y'); - $month = (int) $dateObj->format('m'); + $year = (int)$dateObj->format('Y'); + $month = (int)$dateObj->format('m'); // 计算季度 if ($month >= 1 && $month <= 3) { - $season = (int) $dateObj->format('Ym') . '01'; // Q1 + $season = (int)$dateObj->format('Ym') . '01'; // Q1 } elseif ($month >= 4 && $month <= 6) { - $season = (int) $dateObj->format('Ym') . '02'; // Q2 + $season = (int)$dateObj->format('Ym') . '02'; // Q2 } elseif ($month >= 7 && $month <= 9) { - $season = (int) $dateObj->format('Ym') . '03'; // Q3 + $season = (int)$dateObj->format('Ym') . '03'; // Q3 } else { - $season = (int) $dateObj->format('Ym') . '04'; // Q4 + $season = (int)$dateObj->format('Ym') . '04'; // Q4 } return [ diff --git a/app/service/GoogleAdsClientService.php b/app/service/GoogleAdsClientService.php index 629a6c1..4bb1a1b 100644 --- a/app/service/GoogleAdsClientService.php +++ b/app/service/GoogleAdsClientService.php @@ -5,25 +5,27 @@ namespace app\service; use Google\Ads\GoogleAds\Lib\V18\GoogleAdsClientBuilder; use Google\Ads\GoogleAds\Lib\V18\GoogleAdsException; use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder; + // GoogleAdsClientService.php class GoogleAdsClientService extends BaseService { private $googleAdsClient; + private $googleAdsClientWithloginCustomerId; - public function __construct($customerId = null) + public function __construct($refreshToken = null, $loginCustomerId = null) { // 如果没有传入 customerId,则从环境变量中获取 - if ($customerId) { - $this->customerId = $customerId; - } else { - $this->customerId = getenv('GOOGLE_ADS_CUSTOMER_ID'); - } +// if ($customerId) { +// $this->customerId = $customerId; +// } else { +// $this->customerId = getenv('GOOGLE_ADS_CUSTOMER_ID'); +// } // 从数据库获取 refreshToken - $refreshToken = $this->getRefreshTokenFromDatabase($this->customerId); +// $refreshToken = $this->getRefreshTokenFromDatabase($this->customerId); if (!$refreshToken) { - throw new \Exception("Access token not found for customerId: " . $this->customerId); + throw new \Exception("Access token not found for customerId: " . $refreshToken); } // OAuth2 Token Authentication @@ -33,10 +35,19 @@ class GoogleAdsClientService extends BaseService ->build(); // 初始化 Google Ads Client - $this->googleAdsClient = (new GoogleAdsClientBuilder()) - ->fromFile() - ->withOAuth2Credential($oAuth2Credential) - ->build(); + if ($loginCustomerId) { + $this->googleAdsClientWithloginCustomerId = (new GoogleAdsClientBuilder()) + ->fromFile() + ->withOAuth2Credential($oAuth2Credential) + ->withLoginCustomerId($loginCustomerId) + ->build(); + } else { + $this->googleAdsClient = (new GoogleAdsClientBuilder()) + ->fromFile() + ->withOAuth2Credential($oAuth2Credential) + ->build(); + } + } @@ -45,4 +56,9 @@ class GoogleAdsClientService extends BaseService return $this->googleAdsClient; } + public function getGoogleAdsClientWithloginCustomerId() + { + return $this->googleAdsClientWithloginCustomerId; + } + } diff --git a/app/service/GoogleAdsGroupService.php b/app/service/GoogleAdsGroupService.php index e074b6c..7bf333b 100644 --- a/app/service/GoogleAdsGroupService.php +++ b/app/service/GoogleAdsGroupService.php @@ -58,13 +58,14 @@ class GoogleAdsGroupService extends BaseService * @return mixed * @throws ApiException */ - public function runListGroups(int $customerId): mixed + public function runListGroups(int $customerId, $options): mixed { // $googleAdsClient = $this->googleAdsClient; - $googleAdsClient = new GoogleAdsClientService($customerId); +// dump($customerId,$options['refresh_token']); + $googleAdsClient = new GoogleAdsClientService($options['refresh_token'],$options['login_customer_id']); // Creates a single shared budget to be used by the campaigns added below. - $groupsResourceName = self::getGroups($googleAdsClient->getGoogleAdsClient(), $customerId); + $groupsResourceName = self::getGroups($googleAdsClient->getGoogleAdsClientWithloginCustomerId(), $customerId); // dump(json_encode($groupsResourceName)); if (is_array($groupsResourceName)) { self::saveGroups($groupsResourceName); @@ -169,7 +170,7 @@ class GoogleAdsGroupService extends BaseService public function runAddGroup($options): mixed { // $googleAdsClient = $this->googleAdsClient; - $googleAdsClient = new GoogleAdsClientService($customerId); + $googleAdsClient = new GoogleAdsClientService($options['refresh_token']); // Creates a single shared budget to be used by the campaigns added below. $resourceNames = self::addGroup($googleAdsClient->getGoogleAdsClient(), $options['customer_id'], $options['campaign_id']); diff --git a/app/service/GoogleOAuthService.php b/app/service/GoogleOAuthService.php index 9457c38..11bc63f 100644 --- a/app/service/GoogleOAuthService.php +++ b/app/service/GoogleOAuthService.php @@ -5,10 +5,11 @@ namespace app\service; use GuzzleHttp\Client; use think\facade\Db as ThinkDb; use app\model\ThirdUserAdvertiser; +use app\model\ThirdUser; class GoogleOAuthService { - public function getAuthUrl() + public function getAuthUrl($state = '') { $clientId = getenv('GOOGLE_CLIENT_ID'); $redirectUri = getenv('GOOGLE_REDIRECT_URI'); @@ -17,17 +18,30 @@ class GoogleOAuthService $accessType = 'offline'; // $state = 'state_parameter_passthrough_value'; // 可选,保护防止CSRF - // 生成随机的state参数,防止CSRF攻击 -// $state = bin2hex(random_bytes(16)); // 生成一个随机字符串 + // 将state保存到会话或数据库中,稍后验证 // $_SESSION['oauth_state'] = $state; // 使用PHP会话来保存state // $authUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=$clientId&redirect_uri=$redirectUri&scope=$scope&response_type=$responseType&state=$state"; - $authUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=$clientId&redirect_uri=$redirectUri&scope=$scope&response_type=$responseType&access_type=$accessType"; + if (!empty($state)) { + // 生成随机的state参数,防止CSRF攻击 +// $state = bin2hex(random_bytes(16)); // 生成一个随机字符串 +// $this->saveThirdUser($state); + $authUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=$clientId&redirect_uri=$redirectUri&scope=$scope&response_type=$responseType&access_type=$accessType&state=$state"; + } else { + $authUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=$clientId&redirect_uri=$redirectUri&scope=$scope&response_type=$responseType&access_type=$accessType"; + } return $authUrl; } + public function getThirdUserByRandomCode($state) + { + $tableName = 'bps_third_user'; + $tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'bps' . $tableName; + $result = ThinkDb::name($tableName)->where('random_code', $state)->find(); + return $result; + } public function getRefreshToken($authCode) { @@ -45,7 +59,94 @@ class GoogleOAuthService return json_decode($response->getBody(), true); } - public function saveRefreshToken($refreshToken, $accessToken) + public function saveThirdUser($state) + { + //发起授权前,新记录到bps_third_user表 + $data = [ + 'random_code' => $state, + 'is_default' => 't', + 'third_type' => 'google', + ]; + $tableName = 'bps_third_user'; + $tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'bps' . $tableName; + + $sql = " INSERT INTO {$tableName} + (random_code, is_default, third_type) + VALUES (:random_code, :is_default, :third_type"; + ThinkDb::execute($sql, $data); + } + + public function getCustomerList($third_user_id = 0) + { + $tableName = 'bps_third_user_advertiser'; + $tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'bps' . $tableName; + + $sql = "SELECT * FROM {$tableName} WHERE doc_ = :third_user_id"; + $data = ['third_user_id' => $third_user_id]; + + return ThinkDb::query($sql, $data); + } + + //保存或更新某个主体部广告账号 + public function saveThirdUserAdvertiser($customer_id, $third_user_id,$login_customer_id,$customer_name = '') + { + // 确保 customer_id 和 third_user_id 是字符串 + $customer_id = (string) $customer_id; + $tableName = 'bps_third_user_advertiser'; + $tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'bps' . $tableName; + $data = [ + 'doc_' => $third_user_id, + 'advertiser_id' => $customer_id, + 'advertiser_name' => $customer_name, + 'google_login_customer_id' => $login_customer_id, + ]; + + $sql = " + INSERT INTO {$tableName} + (advertiser_id,advertiser_name, doc_,google_login_customer_id) + VALUES (:advertiser_id, :advertiser_name,:doc_,:google_login_customer_id) + ON CONFLICT (advertiser_id,doc_) + DO UPDATE SET + advertiser_name = EXCLUDED.advertiser_name, + google_login_customer_id = EXCLUDED.google_login_customer_id + "; +// dump($sql,$data); + ThinkDb::execute($sql, $data); + } + + //绑定某个主体的广告账号 + public function bindThirdUserAdvertiser($customer_id, $third_user_id) + { + $tableName = 'bps_third_user'; + $tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'bps' . $tableName; + $data = [ + 'id' => $third_user_id, + 'user_id' => $customer_id,]; + $sql = " + UPDATE {$tableName} + SET user_id = :user_id + WHERE id = :id + "; + ThinkDb::execute($sql, $data); + } + + public function updateThirdUser($state, $refreshToken): void + { + $tableName = 'bps_third_user'; + $tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'bps' . $tableName; + $data = [ + 'access_token' => $refreshToken, + 'random_code' => $state,]; + $sql = " + UPDATE {$tableName} + SET access_token = :access_token + WHERE random_code = :random_code + "; + ThinkDb::execute($sql, $data); + } + + + public function saveRefreshToken($refreshToken, $state = '') { // 使用ThinkORM保存数据到bps_third_user表 // $thirdUser = new \App\Models\ThirdUser(); @@ -62,28 +163,35 @@ class GoogleOAuthService ]; $tableName = 'bps_third_user'; $tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'bps' . $tableName; + if (!empty($state)) { - $sql = " + $thirdUser = ThirdUser::where('user_id', $state)->find(); // 获取第一个结果 + // dump($thirdUser); return ($uid); + if ($thirdUser) { + $data['id'] = $thirdUser->id; + $sql = " + UPDATE {$tableName} + SET + access_token = :access_token, + is_default = :is_default, + third_type = :third_type + WHERE id = :id + "; + } else { + $data['user_id'] = $state; + $sql = " + INSERT INTO {$tableName} + (access_token, is_default, third_type, user_id) + VALUES (:access_token, :is_default, :third_type, :user_id) + "; + } + } else { + $sql = " INSERT INTO {$tableName} (access_token, is_default, third_type) VALUES (:access_token, :is_default, :third_type) - ON CONFLICT (user_id) - DO UPDATE SET - access_token = EXCLUDED.access_token, - is_default = EXCLUDED.is_default, "; -// $sql = " -// INSERT INTO {$tableName} -// (access_token, is_default, random_code, third_type, user_id, facebook_user_id) -// VALUES (:access_token, :is_default, :random_code, :third_type, :user_id, :facebook_user_id) -// ON CONFLICT (user_id) -// DO UPDATE SET -// access_token = EXCLUDED.access_token, -// is_default = EXCLUDED.is_default, -// random_code = EXCLUDED.random_code, -// third_type = EXCLUDED.third_type, -// "; - + } ThinkDb::execute($sql, $data); } @@ -103,7 +211,7 @@ class GoogleOAuthService } - public function revokeToken($accessToken) + public function revokeToken($accessToken, $third_user_id) { $client = new Client(); $client->post('https://oauth2.googleapis.com/revoke', [ @@ -113,16 +221,19 @@ class GoogleOAuthService ]); // 在数据库中删除或标记该`access_token(其实是refresh_token)`为无效 -// ThirdUserModel::where('access_token', $accessToken)->delete(); - $tableName = "bps.bps_third_user"; - $sql = "UPDATE {$tableName} SET access_token = '' WHERE access_token = :access_token"; + ThirdUserAdvertiser::where('doc_', $third_user_id)->delete(); - $data = [ - 'access_token' => $accessToken // 这里的 $accessToken 是您想要匹配的值 + $tableName = 'bps_third_user'; + $tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'bps' . $tableName; + $sql = "UPDATE {$tableName} SET access_token = :access_token WHERE id = :id"; + $data = [ + 'access_token' => '', // 这里的 $accessToken 是您想要匹配的值 + 'id' => $third_user_id, // 这里的 $accessToken 是您想要匹配的值 ]; - // 执行 SQL 语句 - ThinkDb::execute($sql, $data); + $result = ThinkDb::execute($sql, $data); +// ThirdUser::where('access_token', $accessToken)->delete(); + } public function useRefreshToken($refreshToken) @@ -152,7 +263,7 @@ class GoogleOAuthService $customers = ThirdUserAdvertiser::alias('tua') ->join('bps.bps_third_user tu', 'tua.doc_ = tu.id') // 连接 bps_third_user 表 ->where('tu.third_type', 'google') // 筛选 third_type 为 google 的记录 - ->field('tua.advertiser_id') // 获取 advertiser_id 字段 + ->field('tua.advertiser_id as customer_id,tua.google_login_customer_id as login_customer_id, tu.access_token as refresh_token') // 获取 advertiser_id 字段 ->select(); // 执行查询 // 如果没有找到符合条件的广告主,抛出异常 @@ -162,7 +273,7 @@ class GoogleOAuthService } // 转换为简单的数组(提取 advertiser_id) - return $customers->column('advertiser_id'); + return $customers->toArray(); } diff --git a/config/event.php b/config/event.php index 81b80a1..4a076bc 100644 --- a/config/event.php +++ b/config/event.php @@ -8,6 +8,7 @@ use app\event\GoogleAdsGroups; use app\event\GoogleAdsAds; use app\event\GoogleAdsAssets; use app\event\GoogleAdsAssetRelations; +use app\event\GoogleAdsCustomers; use app\event\GoogleAdsDateDatas; @@ -40,6 +41,9 @@ return [ GoogleAdsAssetRelations::VIDEOASSET => [ [GoogleAdsAssetRelations::class, 'getVideoAssetRelations'], ], + GoogleAdsCustomers::CUSTOMERADD => [ + [GoogleAdsCustomers::class, 'addCustomers'], + ], ]; diff --git a/config/process.php b/config/process.php index 8b29ff2..eefb612 100644 --- a/config/process.php +++ b/config/process.php @@ -21,7 +21,7 @@ global $argv; return [ 'webman' => [ 'handler' => Http::class, - 'listen' => 'http://0.0.0.0:8042', + 'listen' => 'http://0.0.0.0:8045', 'count' => cpu_count() * 4, 'user' => '', 'group' => '', diff --git a/config/route.php b/config/route.php index 647f94f..560f6f0 100644 --- a/config/route.php +++ b/config/route.php @@ -15,6 +15,7 @@ //use app\controller\IndexController; use app\controller\AdController; use app\controller\OAuthController; +use app\controller\CustomerController; use app\controller\GoogleAdsController; use support\Request; use Webman\Route; @@ -68,17 +69,25 @@ Route::group('/googleads', function () { })->middleware([ app\middleware\OauthCheck::class, ]); + Route::group('/customer', function () { + Route::post('/list', [CustomerController::class, 'getCustomerList']); + Route::post('/bind', [CustomerController::class, 'bindAdvertiser']); + Route::post('/list_resource', [CustomerController::class, 'accessibleCustomers']); + Route::post('/list_tree', [CustomerController::class, 'accountHierarchy']); + })->middleware([ + app\middleware\OauthCheck::class, + ]); Route::group('/auth', function () { Route::get('/code', [OAuthController::class, 'getAuthCode']); Route::post('/callback', [OAuthController::class, 'handleCallback']); Route::post('/refresh_token_get', [OAuthController::class, 'getRefreshToken']); Route::post('/refresh_token_use', [OAuthController::class, 'useRefreshToken']); - Route::post('/refresh_token_test', [OAuthController::class, 'testRefreshToken'])->middleware([ + Route::post('/refresh_token_test', [OAuthController::class, 'testRefreshToken']); + Route::post('/refresh_token_revoke', [OAuthController::class, 'revokeRefreshToken']); + })->middleware([ app\middleware\Jwt::class, ]); - Route::post('/refresh_token_revoke', [OAuthController::class, 'revokeRefreshToken']); - }); }); @@ -128,9 +137,7 @@ Route::group('/googleads', function () { Route::group('/account_link', function () { Route::post('/create', [GoogleAdsController::class, 'createLinkManagerToClient']); }); - Route::group('/account_link', function () { - Route::post('/list', [GoogleAdsController::class, 'accessibleCustomers']); - }); + Route::group('/datedata', function () { Route::post('/list', [GoogleAdsController::class, 'listDateDatas']); });