diff --git a/app/controller/GoogleAdsController.php b/app/controller/GoogleAdsController.php index deba0aa..2813b45 100644 --- a/app/controller/GoogleAdsController.php +++ b/app/controller/GoogleAdsController.php @@ -2,6 +2,8 @@ namespace app\controller; +use app\model\Campaign as CampaignModel; +use app\model\CampaignBudget; use Google\ApiCore\ApiException; use support\Request; use app\service\GoogleAdsCampaignService; @@ -10,6 +12,7 @@ use app\service\GoogleAdsAdService; use app\service\GoogleAdsAccountService; use support\Response; use DI\Annotation\Inject; +use think\facade\Db as ThinkDb; class GoogleAdsController { @@ -60,6 +63,36 @@ class GoogleAdsController return $this->getCampaigns($options); } + public function listGroups(Request $request) + { + $options = $request->all(); + + // 继续处理 Google Ads API 操作 + return $this->getGroups($options); + } + + public function listAds(Request $request) + { + $options = $request->all(); + + // 继续处理 Google Ads API 操作 + return $this->getAds($options); + } + + public function listDateDatas(Request $request) + { + $options = $request->all(); + return $this->getDateDatas($options); + } + + public function listCampaignsNext(Request $request) + { + $options = $request->all(); + + // 继续处理 Google Ads API 操作 + return $this->getCampaignsNext($options); + } + public function deleteCampaign(Request $request) { $options = $request->all(); @@ -118,7 +151,6 @@ class GoogleAdsController } - public function listResponsiveSearchAds(Request $request) { $options = $request->all(); @@ -126,6 +158,7 @@ class GoogleAdsController // 继续处理 Google Ads API 操作 return $this->getResponsiveSearchAds($options); } + public function listGoogleAdsFields(Request $request) { $options = $request->all(); @@ -202,6 +235,45 @@ class GoogleAdsController return $this->successResponse(['campaigns_list' => $resourceName]); } + /** + * get groups + * @throws ApiException + */ + public function getGroups($options): Response + { + $resourceName = $this->googleAdsGroupService->runListGroups($options['customer_id']); + return $this->successResponse(['groups_list' => $resourceName]); + } + /** + * get groups + * @throws ApiException + */ + public function getAds($options): Response + { + $resourceName = $this->googleAdsAdService->runListAds($options['customer_id']); + return $this->successResponse(['groups_list' => $resourceName]); + } + + /** + * get date datas + * @throws ApiException + */ + public function getDateDatas($options): Response + { + $resourceName = $this->googleAdsCampaignService->runListDateDatas($options['customer_id'], '2024-12-18'); + return $this->successResponse(['date_datas_list' => $resourceName]); + } + + /** + * get campaigns Next + * @throws ApiException + */ + public function getCampaignsNext($options): Response + { + $data = $this->googleAdsCampaignService->runListCampaignsNext($options); + return $this->successResponse($data); + } + /** * 删除广告系列 * @throws ApiException @@ -252,13 +324,14 @@ class GoogleAdsController $resourceName = $this->googleAdsGroupService->runUpdateGroup($options); return $this->successResponse(['group_updated' => $resourceName]); } + /** * 更新广告 * @throws ApiException */ public function modifyAd($options): Response { - $adGroupStatus = [ + $adGroupStatus = [ 0, // UNSPECIFIED 1, // UNKNOWN 2, // ENABLED @@ -292,6 +365,7 @@ class GoogleAdsController $googleAdsFieldData = $this->googleAdsAdService->runSearchForGoogleAdsFields($options); return $this->successResponse(['ads_fields' => $googleAdsFieldData]); } + /** * get campaigns * @throws ApiException diff --git a/app/event/GoogleAdsAds.php b/app/event/GoogleAdsAds.php new file mode 100644 index 0000000..86067a5 --- /dev/null +++ b/app/event/GoogleAdsAds.php @@ -0,0 +1,280 @@ +all(); + + // 继续处理 Google Ads API 操作 + return $this->getAds($options); + } + + /** + * get groups + * @throws ApiException + */ + public function getAds($options): Response + { + $resourceName = $this->googleAdsAdService->runListAds($options['customer_id']); + + 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/GoogleAdsCampaigns.php b/app/event/GoogleAdsCampaigns.php new file mode 100644 index 0000000..da9108e --- /dev/null +++ b/app/event/GoogleAdsCampaigns.php @@ -0,0 +1,281 @@ +all(); + + // 继续处理 Google Ads API 操作 + return $this->getCampaigns($options); + } + + /** + * get campaigns + * @throws ApiException + */ + public function getCampaigns($options): Response + { + $resourceName = $this->googleAdsCampaignService->runListCampaigns($options['customer_id']); + + return $this->successResponse(['campaigns_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/GoogleAdsDateDatas.php b/app/event/GoogleAdsDateDatas.php new file mode 100644 index 0000000..f0e5dce --- /dev/null +++ b/app/event/GoogleAdsDateDatas.php @@ -0,0 +1,280 @@ +all(); + + // 继续处理 Google Ads API 操作 + return $this->getCampaigns($options); + } + + /** + * get date datas + * @throws ApiException + */ + public function getDateDatas($options) + { + $this->googleAdsCampaignService->runListDateDatas($options['customer_id'], $options['date']); +// return $this->successResponse(['data' => $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 new file mode 100644 index 0000000..291e7c7 --- /dev/null +++ b/app/event/GoogleAdsGroups.php @@ -0,0 +1,281 @@ +all(); + + // 继续处理 Google Ads API 操作 + return $this->getGroups($options); + } + + /** + * get groups + * @throws ApiException + */ + public function getGroups($options): Response + { + $resourceName = $this->googleAdsGroupService->runListGroups($options['customer_id']); + + return $this->successResponse(['groups_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/process/UpdateGoogleAdsTask.php b/app/process/UpdateGoogleAdsTask.php index e550a4a..41ab4c9 100644 --- a/app/process/UpdateGoogleAdsTask.php +++ b/app/process/UpdateGoogleAdsTask.php @@ -3,8 +3,10 @@ namespace app\process; -use app\event\TiktokAds; -use app\event\TiktokAdsDetails; +use app\event\GoogleAdsCampaigns; +use app\event\GoogleAdsGroups; +use app\event\GoogleAdsAds; +use app\event\GoogleAdsDateDatas; use Webman\Event\Event; use Workerman\Crontab\Crontab; @@ -17,25 +19,59 @@ class UpdateGoogleAdsTask public function onWorkerStart() { - // 每15分钟执行一次 - new Crontab('*/15 * * * * *', function () { -// dump(date('Y-m-d H:i:s') . '更新' . TiktokAdsDetails::type . '开始'); -// Event::emit(TiktokAdsDetails::type, null); + // 每15分钟执行一次 + new Crontab('10 */1 * * * *', function () { + + $dayBeforeYesterdayStart = date('Y-m-d', strtotime('-2 day')); + dump($dayBeforeYesterdayStart . '更新' . GoogleAdsDateDatas::type . '开始'); + Event::emit(GoogleAdsDateDatas::type, ['customer_id' => 4060397299, 'date' => $dayBeforeYesterdayStart]); } ); - // 每12分钟执行一次 - new Crontab('0 */12 * * * *', function () { -// dump(date('Y-m-d H:i:s') . '更新' . HuPu::type . '开始'); -// Event::emit(HuPu::type, null); + // 每15分钟执行一次 + new Crontab('20 */1 * * * *', function () { + $yesterdayStart = date('Y-m-d', strtotime('-1 day')); + dump($yesterdayStart . '更新' . GoogleAdsDateDatas::type . '开始'); + Event::emit(GoogleAdsDateDatas::type, ['customer_id' => 4060397299, 'date' => $yesterdayStart]); + } + ); + + // 每15分钟执行一次 + new Crontab('30 */1 * * * *', function () { + //获取今天的 0 点的YYYY-MM-DD格式 + $todayStart = date('Y-m-d', strtotime('0 day')); + dump($todayStart . '更新' . GoogleAdsDateDatas::type . '开始'); + Event::emit(GoogleAdsDateDatas::type, ['customer_id' => 4060397299, 'date' => $todayStart]); + } + ); + // 每15分钟执行一次 + new Crontab('40 */1 * * * *', function () { + dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsCampaigns::type . '开始'); + Event::emit(GoogleAdsCampaigns::type, ['customer_id'=>4060397299]); + } + ); + + // 每15分钟执行一次 + new Crontab('50 */1 * * * *', function () { + dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsGroups::type . '开始'); + Event::emit(GoogleAdsGroups::type, ['customer_id'=>4060397299]); + } + ); + + // 每15分钟执行一次 + new Crontab('55 */1 * * * *', function () { + dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAds::type . '开始'); + Event::emit(GoogleAdsAds::type, ['customer_id'=>4060397299]); + } + ); + + // 每2分钟执行一次 + new Crontab('0 */1 * * * *', function () { +// dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsCampaigns::type . '开始'); +// Event::emit(GoogleAdsCampaigns::type, ['customer_id'=>4060397299]); -// dump(date('Y-m-d H:i:s') . '更新' . DouBan::type . '开始'); -// Event::emit(DouBan::type, null); -// -// dump(date('Y-m-d H:i:s') . '更新' . Itzhijia::type . '开始'); -// Event::emit(Itzhijia::type, null); }); // 每30分钟执行一次 diff --git a/app/service/GoogleAdsAdService.php b/app/service/GoogleAdsAdService.php index e4379e9..9039a35 100644 --- a/app/service/GoogleAdsAdService.php +++ b/app/service/GoogleAdsAdService.php @@ -31,7 +31,9 @@ use Google\Ads\GoogleAds\V18\Services\GoogleAdsRow; use Google\Ads\GoogleAds\V18\Services\MutateAdsRequest; use Google\Ads\GoogleAds\V18\Services\SearchGoogleAdsFieldsRequest; use Google\Ads\GoogleAds\V18\Services\SearchGoogleAdsRequest; +use Google\Ads\GoogleAds\V18\Services\SearchGoogleAdsStreamRequest; use Google\Protobuf\Internal\RepeatedField; +use think\facade\Db as ThinkDb; use Google\ApiCore\ApiException; @@ -53,9 +55,111 @@ class GoogleAdsAdService ->withOAuth2Credential($oAuth2Credential) ->build(); } + + + + /* @param int $customerId the customer ID + * @param $options + * @return mixed + * @throws ApiException + */ + public function runListAds(int $customerId): mixed + { + + $googleAdsClient = $this->googleAdsClient; + // Creates a single shared budget to be used by the campaigns added below. + $groupadsResourceName = self::getAds($googleAdsClient, $customerId); + dump(json_encode($groupadsResourceName)); + if (is_array($groupadsResourceName)) { + self::saveAds($groupadsResourceName); + } + + return $groupadsResourceName; + } + + + /** + * 在数据库中保存广告系列信息 + * @param $groupadsResourceName + * @return void + */ + public static function saveAds($groupadsResourceName) + { + $tableName = 'bps_google_ads_ad'; + $tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'public' . $tableName; + foreach ($groupadsResourceName as $data){ + $sql = "INSERT INTO {$tableName} + (ad_id, ad_group_id, customer_id, ad_name, status, resource_name) + VALUES (:ad_id, :ad_group_id, :customer_id, :ad_name, :status, :resource_name) + ON CONFLICT (ad_id) + DO UPDATE SET + ad_group_id = EXCLUDED.ad_group_id, + customer_id = EXCLUDED.customer_id, + ad_name = EXCLUDED.ad_name, + status = EXCLUDED.status, + resource_name = EXCLUDED.resource_name, + update_at = EXCLUDED.update_at"; +// dump($sql); + ThinkDb::execute($sql, $data); + } + } + + + /** + * Runs the example. + * + * @param GoogleAdsClient $googleAdsClient the Google Ads API client + * @param int $customerId the customer ID + */ + // [START get_campaigns] + + public static function getAds(GoogleAdsClient $googleAdsClient, int $customerId) + { + $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); + // Creates a query that retrieves all groups. + +// $response = $googleAdsServiceClient->search($customerId, $query); + $query = "SELECT + ad_group_ad.ad.id, + ad_group.id, + customer.id, + ad_group_ad.ad.name, + ad_group_ad.status, + ad_group_ad.ad.resource_name + FROM ad_group_ad + WHERE + ad_group_ad.status != 'REMOVED' "; + // Issues a search stream request. + /** @var GoogleAdsServerStreamDecorator $stream */ + $stream = $googleAdsServiceClient->searchStream( + SearchGoogleAdsStreamRequest::build($customerId, $query) + ); + $resourceNames = []; + // Iterates over all rows in all messages and prints the requested field values for + // the campaign in each row. + foreach ($stream->iterateAllElements() as $googleAdsRow) { + /** @var GoogleAdsRow $googleAdsRow */ + // 假设 $googleAdsRow 是从 Google Ads API 中获取的对象 +// $finalUrlsList = $googleAdsRow->getAdGroupAd()->getAd()->getFinalUrls(); + // 将最终的 URL 列表转换为 PHP 数组 +// $finalUrlsArray = iterator_to_array($finalUrlsList); + $resourceName['ad_id'] = $googleAdsRow->getAdGroupAd()->getAd()->getId(); + $resourceName['ad_name'] = $googleAdsRow->getAdGroupAd()->getAd()->getName(); + $resourceName['ad_group_id'] = $googleAdsRow->getAdGroup()->getId(); + $resourceName['customer_id'] = $googleAdsRow->getCustomer()->getId(); +// $resourceName['final_urls'] = $finalUrlsArray; + $resourceName['status'] = $googleAdsRow->getAdGroupAd()->getStatus(); + $resourceName['resource_name'] = $googleAdsRow->getAdGroupAd()->getAd()->getResourceName(); + $resourceNames[] = $resourceName; + } + return $resourceNames; + } + + + /** * This example updates the CPC bid and status for a given ad group. To get ad groups, run - * GetAdGroups.php. + * GetAdAds.php. */ /* @param int $customerId the customer ID * @param $options @@ -125,7 +229,7 @@ class GoogleAdsAdService /** * This example updates the CPC bid and status for a given ad group. To get ad groups, run - * GetAdGroups.php. + * GetAdAds.php. */ /* @param int $customerId the customer ID * @param $options @@ -239,7 +343,7 @@ class GoogleAdsAdService /** * This example updates the CPC bid and status for a given ad group. To get ad groups, run - * GetAdGroups.php. + * GetAdAds.php. */ /* @param int $customerId the customer ID * @param $options diff --git a/app/service/GoogleAdsCampaignService.php b/app/service/GoogleAdsCampaignService.php index 746c2a2..ca9cf55 100644 --- a/app/service/GoogleAdsCampaignService.php +++ b/app/service/GoogleAdsCampaignService.php @@ -27,6 +27,12 @@ use Google\Ads\GoogleAds\V18\Services\GoogleAdsRow; use Google\Ads\GoogleAds\V18\Services\MutateCampaignsRequest; use Google\Ads\GoogleAds\V18\Services\MutateCampaignBudgetsRequest; use Google\Ads\GoogleAds\V18\Services\SearchGoogleAdsStreamRequest; +use Google\Ads\GoogleAds\V18\Services\SearchGoogleAdsRequest; +use Google\Ads\GoogleAds\V18\Services\SearchGoogleAdsResponse; +use app\model\Campaign as CampaignModel; +use app\model\DayData as DayDataModel; +use think\facade\Db as ThinkDb; + use Google\ApiCore\ApiException; class GoogleAdsCampaignService @@ -184,13 +190,37 @@ class GoogleAdsCampaignService */ public function runListCampaigns(int $customerId): mixed { + $googleAdsClient = $this->googleAdsClient; // Creates a single shared budget to be used by the campaigns added below. $campaignsResourceName = self::getCampaigns($googleAdsClient, $customerId); +// dump(json_encode($campaignsResourceName)); + if (is_array($campaignsResourceName)) { + self::saveCampaigns($campaignsResourceName); + } return $campaignsResourceName; } + /* @param int $customerId the customer ID + * @param $options + * @return mixed + * @throws ApiException + */ + public function runListDateDatas(int $customerId, $date): mixed + { + + $googleAdsClient = $this->googleAdsClient; + // Creates a single shared budget to be used by the campaigns added below. + $dayResourceName = self::getDateDatas($googleAdsClient, $customerId, $date); +// dump(json_encode($dayResourceName)); + if (is_array($dayResourceName)) { + self::saveDateDatas($dayResourceName); + } + + return $dayResourceName; + } + /** * Runs the example. @@ -204,7 +234,19 @@ class GoogleAdsCampaignService { $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); // Creates a query that retrieves all campaigns. - $query = 'SELECT campaign.id, campaign.name FROM campaign ORDER BY campaign.id'; +// $query = 'SELECT campaign.id,campaign.name,campaign.status,campaign.campaign_budget,campaign_budget.id,campaign_budget.name,campaign_budget.amount_micros FROM campaign ORDER BY campaign.id'; + $query = "SELECT + campaign.id, + campaign.name, + campaign.status, + campaign.advertising_channel_type, + campaign.start_date, + campaign.end_date, + campaign_budget.amount_micros, + customer.id + FROM campaign + WHERE + campaign.status != 'REMOVED' "; // Issues a search stream request. /** @var GoogleAdsServerStreamDecorator $stream */ $stream = $googleAdsServiceClient->searchStream( @@ -215,17 +257,241 @@ class GoogleAdsCampaignService // the campaign in each row. foreach ($stream->iterateAllElements() as $googleAdsRow) { /** @var GoogleAdsRow $googleAdsRow */ - printf( - "Campaign with ID %d and name '%s' was found.%s", - $googleAdsRow->getCampaign()->getId(), - $googleAdsRow->getCampaign()->getName(), - PHP_EOL - ); - $resourceNames[$googleAdsRow->getCampaign()->getId()] = $googleAdsRow->getCampaign()->getName(); +// printf( +// "Campaign with ID %d and name '%s' was found.BudgetID %s: %d %s", +// $googleAdsRow->getCampaign()->getId(), +// $googleAdsRow->getCampaign()->getName(), +// $googleAdsRow->getCampaignBudget()->getName(), +// $googleAdsRow->getCampaignBudget()->getAmountMicros(), +// +// $googleAdsRow->getCampaignBudget()->getName(), +// PHP_EOL +// ); + $resourceName['campaign_id'] = $googleAdsRow->getCampaign()->getId(); + $resourceName['campaign_name'] = $googleAdsRow->getCampaign()->getName(); + $resourceName['advertising_channel_type'] = $googleAdsRow->getCampaign()->getAdvertisingChannelType(); + $resourceName['status'] = $googleAdsRow->getCampaign()->getStatus(); + $resourceName['start_date'] = $googleAdsRow->getCampaign()->getStartDate(); + $resourceName['end_date'] = $googleAdsRow->getCampaign()->getEndDate(); + $resourceName['budget_amount_micros'] = $googleAdsRow->getCampaignBudget()->getAmountMicros(); + $resourceName['customer_id'] = $googleAdsRow->getCustomer()->getId(); + $resourceNames[] = $resourceName; } return $resourceNames; } - // [END get_campaigns] + + + /** + * Runs the example. + * + * @param GoogleAdsClient $googleAdsClient the Google Ads API client + * @param int $customerId the customer ID + */ + // [START get_campaigns] + + public static function getDateDatas(GoogleAdsClient $googleAdsClient, int $customerId, $date = '2024-12-19') + { + $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); + + // Creates a query that retrieves all campaigns. + $query = "SELECT ad_group_ad.ad.id, ad_group_ad.ad.name, ad_group_ad.ad.resource_name, ad_group.id, campaign.id, customer.id, metrics.clicks, metrics.cost_micros, metrics.conversions, metrics.conversions_value, metrics.impressions FROM ad_group_ad WHERE segments.date = '{$date}' ORDER BY ad_group.id ASC LIMIT 10000"; +// dump($query);return; + // Issues a search stream request. + /** @var GoogleAdsServerStreamDecorator $stream */ + $stream = $googleAdsServiceClient->searchStream( + SearchGoogleAdsStreamRequest::build($customerId, $query) + ); + $resourceNames = []; + // Iterates over all rows in all messages and prints the requested field values for + // the campaign in each row. + foreach ($stream->iterateAllElements() as $googleAdsRow) { + /** @var GoogleAdsRow $googleAdsRow */ + $resourceName['ad_id'] = $googleAdsRow->getAdGroupAd()->getAd()->getId(); + $resourceName['customer_id'] = $googleAdsRow->getCustomer()->getId(); + $resourceName['ad_name'] = $googleAdsRow->getAdGroupAd()->getAd()->getName(); + $resourceName['ad_resource_name'] = $googleAdsRow->getAdGroupAd()->getAd()->getResourceName(); + $resourceName['ad_group_id'] = $googleAdsRow->getAdGroup()->getId(); + $resourceName['campaign_id'] = $googleAdsRow->getCampaign()->getId(); + $resourceName['clicks'] = $googleAdsRow->getMetrics()->getClicks(); + $resourceName['cost_micros'] = $googleAdsRow->getMetrics()->getCostMicros(); + $resourceName['conversions'] = $googleAdsRow->getMetrics()->getConversions(); + $resourceName['conversions_value'] = $googleAdsRow->getMetrics()->getConversionsValue(); + $resourceName['impressions'] = $googleAdsRow->getMetrics()->getImpressions(); + $resourceName['date'] = $date; +// $resourceName['budget_id'] = $googleAdsRow->getCampaignBudget()->getId(); + $resourceNames[] = $resourceName; + } + return $resourceNames; + } + + /** + * Runs the example. + * + * @param GoogleAdsClient $googleAdsClient the Google Ads API client + * @param int $customerId the customer ID + */ + // [START get_campaigns] + + /** + * 在数据库中保存广告系列信息 + * @param $campaignsResourceName + * @return void + */ + public static function saveCampaigns($campaignsResourceName) + { + $tableName = 'bps_google_ads_campaign'; + $tableName = getenv('DB_PG_SCHEMA')? getenv('DB_PG_SCHEMA').'.'.$tableName: 'public'.$tableName; + foreach ($campaignsResourceName as $data) { + $sql = "INSERT INTO {$tableName} + (campaign_id, customer_id, campaign_name, status, advertising_channel_type, start_date, end_date, budget_amount_micros) + VALUES (:campaign_id, :customer_id, :campaign_name, :status, :advertising_channel_type, :start_date, :end_date, :budget_amount_micros)"; + ThinkDb::execute($sql, $data); + } + } + + + /** + * 在数据库中保存广告系列信息 + * @param $campaignsResourceName + * @return void + */ + public static function saveDateDatas($dayResourceName) + { +// dump($campaignsResourceName); +// $dayDataModel = new DayDataModel; +// $list = $campaignsResourceName; +// ThinkDb::execute("update public.ad_ga_campaign_budgets set name='thinkphp999' where id=2"); + +// $dayDataModel->saveAll($dayResourceName); +// $dayResourceName = [ +// 'ad_id' => 725809753919, +// 'customer_id' => 4060397299, +// 'ad_name' => '', +// 'ad_resource_name' => 'customers/4060397299/ads/725809753919', +// 'ad_group_id' => 171788435506, +// 'campaign_id' => 22026467676, +// 'clicks' => 0, +// 'cost_micros' => 0, +// 'conversions' => 0, +// 'conversions_value' => 0, +// 'impressions' => 0, +// 'date' => '2024-12-18', +// ]; + foreach ($dayResourceName as $data) { + $sql = "INSERT INTO public.bps_google_ad_day_data (ad_id, customer_id, ad_name, ad_resource_name, ad_group_id, campaign_id, clicks, cost_micros, conversions, conversions_value, impressions, date) + VALUES (:ad_id, :customer_id, :ad_name, :ad_resource_name, :ad_group_id, :campaign_id, :clicks, :cost_micros, :conversions, :conversions_value, :impressions, :date)"; + ThinkDb::execute($sql, $data); + } + } + + /* @param int $customerId the customer ID + * @param $options + * @return mixed + * @throws ApiException + */ + public function runListCampaignsNext($options): mixed + { + $googleAdsClient = $this->googleAdsClient; + // Creates a single shared budget to be used by the campaigns added below. + $campaigns = self::getCampaignsNext($googleAdsClient, $options['customer_id'], $options['page_token']); + + return $campaigns; + } + + /** + * 获取广告系列列表,支持分页 + * + * @param GoogleAdsClient $googleAdsClient + * @param string $customerId 客户 ID + * @param null $pageToken 下一页的令牌 + * @return array 广告系列列表及下一页令牌 + */ + public function getCampaignsNext(GoogleAdsClient $googleAdsClient, $customerId, $pageToken = null) + { + $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); + + // 创建查询,获取广告系列信息 + $query = "SELECT campaign.id, campaign.name FROM campaign ORDER BY campaign.id"; + + // 初始化分页参数 + $pageToken = null; +// $pageSize = 1000; // 每页1000条记录 + $allCampaigns = []; + $totalResultsCount = 0; + $queryResourceConsumption = 0; + + // 创建请求对象 + $request = new SearchGoogleAdsRequest([ + 'customer_id' => $customerId, + 'query' => $query, + 'page_token' => $pageToken, // 分页令牌 +// 'page_size' => $pageSize, // 设置每页的大小 + ]); + // 调用 search 方法进行数据获取 + try { + // 发起查询请求,返回 PagedListResponse + $response = $googleAdsServiceClient->search($request); + + // 获取总结果数 + $totalResultsCount = $response->getTotalResultsCount(); + + // 获取查询消耗的资源 + $queryResourceConsumption = $response->getQueryResourceConsumption(); + + // 获取返回结果(PagedListResponse 类型) + $this->processSearchGoogleAdsResponse($response, $allCampaigns); + + // 获取下一页的分页令牌 + $pageToken = $response->getNextPageToken(); + + } catch (ApiException $e) { + // 处理异常 + printf("API request failed with message: %s\n", $e->getMessage()); + } + + return [ + 'campaigns' => $allCampaigns, + 'totalResultsCount' => $totalResultsCount, + 'queryResourceConsumption' => $queryResourceConsumption + ]; + } + + /** + * 处理 SearchGoogleAdsResponse 数据并提取广告系列信息 + * + * @param SearchGoogleAdsResponse $response 查询响应 + * @param array $allCampaigns 用于存储广告系列的数组 + */ + private function processSearchGoogleAdsResponse(SearchGoogleAdsResponse $response, array &$allCampaigns) + { + // 获取查询结果 + foreach ($response->getResults() as $googleAdsRow) { + /** @var \Google\Ads\GoogleAds\V18\Resources\Campaign $campaign */ + $campaign = $googleAdsRow->getCampaign(); + $allCampaigns[] = [ + 'id' => $campaign->getId(), + 'name' => $campaign->getName(), + ]; + } + + // 输出分页的总结果数 + printf("Total Results Count: %d\n", $response->getTotalResultsCount()); + + // 输出查询资源消耗 + printf("Query Resource Consumption: %d\n", $response->getQueryResourceConsumption()); + + // 输出汇总数据(如果存在) + if ($summaryRow = $response->getSummaryRow()) { + // 处理汇总行(根据需要) + printf("Summary Row Metrics: %s\n", $summaryRow); + } + + // 获取下一页的分页令牌 + $nextPageToken = $response->getNextPageToken(); + if ($nextPageToken) { + printf("Next Page Token: %s\n", $nextPageToken); + } + } /* @param int $customerId the customer ID @@ -291,7 +557,7 @@ class GoogleAdsCampaignService { $googleAdsClient = $this->googleAdsClient; // Creates a single shared budget to be used by the campaigns added below. - $resourceName = self::updateCampaign($googleAdsClient, $options['customer_id'], $options['campaign_id'],$options['status']); + $resourceName = self::updateCampaign($googleAdsClient, $options['customer_id'], $options['campaign_id'], $options['status']); return $resourceName; } @@ -316,7 +582,7 @@ class GoogleAdsCampaignService // 'resource_name' => ResourceNames::forCampaign($customerId, $campaignId), // 'status' => CampaignStatus::PAUSED 'resource_name' => ResourceNames::forCampaign($customerId, $campaignId), - 'status' => $status, + 'status' => $status, ]); // Constructs an operation that will update the campaign with the specified resource name, diff --git a/app/service/GoogleAdsGroupService.php b/app/service/GoogleAdsGroupService.php index 45fbc52..6c65898 100644 --- a/app/service/GoogleAdsGroupService.php +++ b/app/service/GoogleAdsGroupService.php @@ -19,7 +19,10 @@ use Google\Ads\GoogleAds\V18\Errors\GoogleAdsError; use Google\Ads\GoogleAds\V18\Resources\AdGroup; use Google\Ads\GoogleAds\V18\Services\AdGroupOperation; use Google\Ads\GoogleAds\V18\Services\MutateAdGroupsRequest; +use Google\Ads\GoogleAds\V18\Services\SearchGoogleAdsStreamRequest; use Google\ApiCore\ApiException; +use think\facade\Db as ThinkDb; + class GoogleAdsGroupService { @@ -40,6 +43,109 @@ class GoogleAdsGroupService } + /* @param int $customerId the customer ID + * @param $options + * @return mixed + * @throws ApiException + */ + public function runListGroups(int $customerId): mixed + { + + $googleAdsClient = $this->googleAdsClient; + // Creates a single shared budget to be used by the campaigns added below. + $groupsResourceName = self::getGroups($googleAdsClient, $customerId); +// dump(json_encode($groupsResourceName)); + if (is_array($groupsResourceName)) { + self::saveGroups($groupsResourceName); + } + + return $groupsResourceName; + } + + + /** + * 在数据库中保存广告系列信息 + * @param $groupsResourceName + * @return void + */ + public static function saveGroups($groupsResourceName) + { + $tableName = 'bps_google_ads_ad_group'; + $tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'public' . $tableName; + foreach ($groupsResourceName as $data) { + $sql = "INSERT INTO {$tableName} + (ad_group_id, campaign_id, customer_id, ad_group_name, status, cpc_bid_micros) + VALUES (:ad_group_id, :campaign_id, :customer_id, :ad_group_name, :status, :cpc_bid_micros) + ON CONFLICT (ad_group_id) + DO UPDATE SET + campaign_id = EXCLUDED.campaign_id, + customer_id = EXCLUDED.customer_id, + ad_group_name = EXCLUDED.ad_group_name, + status = EXCLUDED.status, + cpc_bid_micros = EXCLUDED.cpc_bid_micros, + update_at = EXCLUDED.update_at"; + + ThinkDb::execute($sql, $data); + } + } + + + /** + * Runs the example. + * + * @param GoogleAdsClient $googleAdsClient the Google Ads API client + * @param int $customerId the customer ID + */ + // [START get_campaigns] + + public static function getGroups(GoogleAdsClient $googleAdsClient, int $customerId) + { + $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); + // Creates a query that retrieves all groups. + +// $response = $googleAdsServiceClient->search($customerId, $query); + $query = "SELECT + ad_group.id, + ad_group.name, + campaign.id, + customer.id, + ad_group.status, + ad_group.cpc_bid_micros + FROM ad_group + WHERE + ad_group.status != 'REMOVED' "; + // Issues a search stream request. + /** @var GoogleAdsServerStreamDecorator $stream */ + $stream = $googleAdsServiceClient->searchStream( + SearchGoogleAdsStreamRequest::build($customerId, $query) + ); + $resourceNames = []; + // Iterates over all rows in all messages and prints the requested field values for + // the campaign in each row. + foreach ($stream->iterateAllElements() as $googleAdsRow) { + /** @var GoogleAdsRow $googleAdsRow */ +// printf( +// "Campaign with ID %d and name '%s' was found.BudgetID %s: %d %s", +// $googleAdsRow->getCampaign()->getId(), +// $googleAdsRow->getCampaign()->getName(), +// $googleAdsRow->getCampaignBudget()->getName(), +// $googleAdsRow->getCampaignBudget()->getAmountMicros(), +// +// $googleAdsRow->getCampaignBudget()->getName(), +// PHP_EOL +// ); + $resourceName['ad_group_id'] = $googleAdsRow->getAdGroup()->getId(); + $resourceName['ad_group_name'] = $googleAdsRow->getAdGroup()->getName(); + $resourceName['cpc_bid_micros'] = $googleAdsRow->getAdGroup()->getCpcBidMicros(); + $resourceName['status'] = $googleAdsRow->getAdGroup()->getStatus(); + $resourceName['campaign_id'] = $googleAdsRow->getCampaign()->getId(); + $resourceName['customer_id'] = $googleAdsRow->getCustomer()->getId(); + $resourceNames[] = $resourceName; + } + return $resourceNames; + } + + /** * This example updates a campaign by setting the status to `PAUSED`. To get campaigns, run * GetCampaigns.php. diff --git a/app/service/GoogleAdsSdkService.php b/app/service/GoogleAdsSdkService.php new file mode 100644 index 0000000..b5c7fcb --- /dev/null +++ b/app/service/GoogleAdsSdkService.php @@ -0,0 +1,136 @@ +fromFile()->build(); + $this->googleAdsClient = (new GoogleAdsClientBuilder())->fromFile()->build(); +// return $config; +// $this->googleAdsClient = new GoogleAdsClient([ +// 'developerToken' => $config['developerToken'], +// 'clientId' => $config['clientId'], +// 'clientSecret' => $config['clientSecret'], +// 'refreshToken' => $config['refreshToken'], +// ]); + } + + // 创建广告预算 + public function createCampaignBudget($customerId, $budgetName, $amount): ?string + { + $campaignBudgetService = $this->googleAdsClient->getCampaignBudgetServiceClient(); + + $campaignBudget = new \Google\Ads\GoogleAds\V18\Resources\CampaignBudget([ + 'name' => $budgetName, + 'amount_micros' => $amount * 1000000, +// 'delivery_method' => 'STANDARD', +// 'status' => 'ENABLED', + ]); + + $operation = new CampaignBudgetOperation(); + $operation->setCreate($campaignBudget); + + try { + $response = $campaignBudgetService->mutateCampaignBudgets($customerId, [$operation]); + return $response->getResults()[0]->getResourceName(); + } catch (\Google\Ads\GoogleAds\Errors\GoogleAdsException $e) { + return $e->getMessage(); + } + } + + // 创建广告系列 + public function createCampaign($customerId, $budgetId, $campaignName) + { + $campaignService = $this->googleAdsClient->getCampaignServiceClient(); + + $campaign = new \Google\Ads\GoogleAds\V18\Resources\Campaign([ + 'name' => $campaignName, + 'advertisingChannelType' => 'SEARCH', + 'status' => 'PAUSED', + 'startDate' => '2024-12-01', + 'endDate' => '2024-12-31', + 'manualCpc' => new \Google\Ads\GoogleAds\V18\Resources\ManualCpc([ + 'enhancedCpcEnabled' => true + ]), + 'campaignBudget' => "customers/{$customerId}/campaignBudgets/{$budgetId}" + ]); + + $operation = new CampaignOperation(); + $operation->setCreate($campaign); + + try { + $response = $campaignService->mutateCampaigns($customerId, [$operation]); + return $response->getResults()[0]->getResourceName(); + } catch (\Google\Ads\GoogleAds\Errors\GoogleAdsException $e) { + return $e->getMessage(); + } + } + + // 创建广告组 + public function createAdGroup($customerId, $campaignId, $adGroupName, $cpcBidMicros) + { + $adGroupService = $this->googleAdsClient->getAdGroupServiceClient(); + + $adGroup = new \Google\Ads\GoogleAds\V18\Resources\AdGroup([ + 'name' => $adGroupName, + 'campaign' => "customers/{$customerId}/campaigns/{$campaignId}", + 'status' => 'PAUSED', + 'cpcBidMicros' => $cpcBidMicros, + 'startDate' => '2024-12-01', + 'endDate' => '2024-12-31', + ]); + + $operation = new AdGroupOperation(); + $operation->setCreate($adGroup); + + try { + $response = $adGroupService->mutateAdGroups($customerId, [$operation]); + return $response->getResults()[0]->getResourceName(); + } catch (\Google\Ads\GoogleAds\Errors\GoogleAdsException $e) { + return $e->getMessage(); + } + } + + // 创建广告 + public function createAd($customerId, $adGroupId, $adName, $headline, $description, $finalUrls) + { + $adService = $this->googleAdsClient->getAdServiceClient(); + + // 创建文本广告 + $textAd = new \Google\Ads\GoogleAds\V18\Resources\AdTextAdInfo([ + 'headline' => $headline, + 'description' => $description, + 'finalUrls' => $finalUrls, + ]); + + $ad = new \Google\Ads\GoogleAds\V18\Resources\Ad([ + 'name' => $adName, + 'adGroup' => "customers/{$customerId}/adGroups/{$adGroupId}", + 'status' => 'PAUSED', + 'textAd' => $textAd, + ]); + + $operation = new AdOperation(); + $operation->setCreate($ad); + + try { + $response = $adService->mutateAds($customerId, [$operation]); + return $response->getResults()[0]->getResourceName(); + } catch (\Google\Ads\GoogleAds\Errors\GoogleAdsException $e) { + return $e->getMessage(); + } + } +} diff --git a/app/service/GoogleAdsService.php b/app/service/GoogleAdsService.php index e0869ab..8fb4abc 100644 --- a/app/service/GoogleAdsService.php +++ b/app/service/GoogleAdsService.php @@ -2,130 +2,221 @@ namespace app\service; -use Google\Ads\GoogleAds\Lib\V18\GoogleAdsClient; -use Google\Ads\GoogleAds\V18\Services\AdGroupOperation; -use Google\Ads\GoogleAds\V18\Services\AdOperation; -use Google\Ads\GoogleAds\V18\Services\CampaignBudgetOperation; -use Google\Ads\GoogleAds\V18\Services\CampaignOperation; +use app\model\Campaign; +use app\model\CampaignBudget; +use app\model\AdGroup; +use app\model\Ad; +use GuzzleHttp\Client as GuzzleClient; +use GuzzleHttp\Exception\RequestException; class GoogleAdsService { - protected $googleAdsClient; + // Google Ads API 基础 URL + private $baseUrl = 'https://googleads.googleapis.com/v17'; - public function __construct() + // Google Ads API 客户 ID + private $customerId; + + private $loginCustomerId; // 增加 login-customer-id 属性 + + // OAuth2 access token + private $accessToken; + + // Developer Token + private $developerToken; + + private $client; + + public function __construct($accessToken, $customerId, $developerToken, $loginCustomerId = null) { - $config = include __DIR__ . '/../../config/google_ads_php.ini'; - $this->googleAdsClient = new GoogleAdsClient([ - 'developerToken' => $config['developer_token'], - 'clientId' => $config['client_id'], - 'clientSecret' => $config['client_secret'], - 'refreshToken' => $config['refresh_token'], - ]); + $this->accessToken = $accessToken; + $this->customerId = $customerId; + $this->developerToken = $developerToken; + $this->loginCustomerId = $loginCustomerId; + + // 初始化 Guzzle 客户端 + $this->client = new GuzzleClient(); } - // 创建广告预算 - public function createCampaignBudget($customerId, $budgetName, $amountMicros) + /** + * 发起 HTTP 请求 + * @param string $method 请求方法(GET, POST, PATCH, DELETE) + * @param string $endpoint API 端点 + * @param array $data 请求数据 + * @return array API 响应 + */ + private function sendRequest($method, $endpoint, $data = []) { - $campaignBudgetService = $this->googleAdsClient->getCampaignBudgetServiceClient(); + // 构建 URL + $url = "{$this->baseUrl}/customers/{$this->customerId}/$endpoint"; - $campaignBudget = new \Google\Ads\GoogleAds\V18\Resources\CampaignBudget([ - 'name' => $budgetName, - 'amountMicros' => $amountMicros, - 'deliveryMethod' => 'STANDARD', - 'status' => 'ENABLED', - ]); - - $operation = new CampaignBudgetOperation(); - $operation->setCreate($campaignBudget); + // 设置请求头 + $headers = [ + 'Authorization' => "Bearer {$this->accessToken}", + 'developer-token' => $this->developerToken, + 'Content-Type' => 'application/json' + ]; + // 如果有广告经理 ID,添加到请求头 + if ($this->loginCustomerId) { + $headers['login-customer-id'] = $this->loginCustomerId; + } + // 调试:输出请求的 URL 和头部信息 +// return [$url, $headers, $data]; + // 使用 Guzzle 发起请求 try { - $response = $campaignBudgetService->mutateCampaignBudgets($customerId, [$operation]); - return $response->getResults()[0]->getResourceName(); - } catch (\Google\Ads\GoogleAds\Errors\GoogleAdsException $e) { - return $e->getMessage(); + $response = $this->client->request($method, $url, [ + 'headers' => $headers, + 'json' => $data + ]); + + + // 获取响应体并解析为数组 + $body = $response->getBody()->getContents(); + return json_decode($body, true); + + } catch (RequestException $e) { + // 处理请求错误 + echo "HTTP Request failed: " . $e->getMessage(); + return []; } } - // 创建广告系列 - public function createCampaign($customerId, $budgetId, $campaignName) + /** + * 创建广告预算并存储到数据库 + */ + public function createCampaignBudget($budgetName, $amount) { - $campaignService = $this->googleAdsClient->getCampaignServiceClient(); + // 构建请求数据 + $data = [ + 'operations' => [ + [ + 'create' => [ + 'name' => $budgetName, + 'amountMicros' => $amount * 1000000, // 转换为微单位 +// 'delivery_method' => 'STANDARD', +// 'status' => 'ENABLED' + ] + ] + ] + ]; +// return $data; + // 调用 sendRequest 方法发起 POST 请求到 mutate 端点 + $response = $this->sendRequest('POST', 'campaignBudgets:mutate', $data); - $campaign = new \Google\Ads\GoogleAds\V18\Resources\Campaign([ + // 存储返回的广告预算数据到数据库 +// if (isset($response['results'][0]['resource_name'])) { +// CampaignBudget::create([ +// 'name' => $response['results'][0]['name'], +// 'amount_micros' => $response['results'][0]['amount_micros'], +// 'status' => $response['results'][0]['status'], +// 'delivery_method' => $response['results'][0]['delivery_method'], +// ]); +// } + + return $response; + } + + /** + * 创建广告系列并存储到数据库 + */ + public function createCampaign($campaignName, $budgetId) + { + // 构建请求数据 + $data = [ 'name' => $campaignName, - 'advertisingChannelType' => 'SEARCH', 'status' => 'PAUSED', - 'startDate' => '2024-12-01', - 'endDate' => '2024-12-31', - 'manualCpc' => new \Google\Ads\GoogleAds\V18\Resources\ManualCpc([ - 'enhancedCpcEnabled' => true - ]), - 'campaignBudget' => "customers/{$customerId}/campaignBudgets/{$budgetId}" - ]); + 'advertising_channel_type' => 'SEARCH', + 'campaign_budget' => "customers/{$this->customerId}/campaignBudgets/{$budgetId}", + 'network_settings' => [ + 'target_google_search' => true, + 'target_search_network' => true, + 'target_content_network' => false, + 'target_partner_search_network' => false + ], + 'start_date' => date('Y-m-d'), + 'end_date' => date('Y-m-d', strtotime('+1 year')) + ]; - $operation = new CampaignOperation(); - $operation->setCreate($campaign); + // 调用 sendRequest 方法发起 POST 请求 + $response = $this->sendRequest('POST', 'campaigns', $data); - try { - $response = $campaignService->mutateCampaigns($customerId, [$operation]); - return $response->getResults()[0]->getResourceName(); - } catch (\Google\Ads\GoogleAds\Errors\GoogleAdsException $e) { - return $e->getMessage(); + // 存储返回的广告系列数据到数据库 + if (isset($response['resource_name'])) { + Campaign::create([ + 'name' => $response['name'], + 'status' => $response['status'], + 'advertising_channel_type' => $response['advertising_channel_type'], + 'campaign_budget' => $response['campaign_budget'], + 'start_date' => $response['start_date'], + 'end_date' => $response['end_date'], + ]); } + + return $response; } - // 创建广告组 - public function createAdGroup($customerId, $campaignId, $adGroupName, $cpcBidMicros) + /** + * 创建广告组并存储到数据库 + */ + public function createAdGroup($adGroupName, $campaignId) { - $adGroupService = $this->googleAdsClient->getAdGroupServiceClient(); - - $adGroup = new \Google\Ads\GoogleAds\V18\Resources\AdGroup([ + // 构建请求数据 + $data = [ 'name' => $adGroupName, - 'campaign' => "customers/{$customerId}/campaigns/{$campaignId}", 'status' => 'PAUSED', - 'cpcBidMicros' => $cpcBidMicros, - 'startDate' => '2024-12-01', - 'endDate' => '2024-12-31', - ]); + 'campaign' => "customers/{$this->customerId}/campaigns/{$campaignId}", + 'type' => 'SEARCH_STANDARD' + ]; - $operation = new AdGroupOperation(); - $operation->setCreate($adGroup); + // 调用 sendRequest 方法发起 POST 请求 + $response = $this->sendRequest('POST', 'adGroups', $data); - try { - $response = $adGroupService->mutateAdGroups($customerId, [$operation]); - return $response->getResults()[0]->getResourceName(); - } catch (\Google\Ads\GoogleAds\Errors\GoogleAdsException $e) { - return $e->getMessage(); + // 存储返回的广告组数据到数据库 + if (isset($response['resource_name'])) { + AdGroup::create([ + 'name' => $response['name'], + 'status' => $response['status'], + 'campaign_id' => $campaignId, + 'type' => $response['type'], + ]); } + + return $response; } - // 创建广告 - public function createAd($customerId, $adGroupId, $adName, $headline, $description, $finalUrls) + /** + * 创建广告并存储到数据库 + */ + public function createAd($adGroupId, $adName, $finalUrl) { - $adService = $this->googleAdsClient->getAdServiceClient(); + // 构建请求数据 + $data = [ + 'ad' => [ + 'expanded_text_ad' => [ + 'headline_part1' => 'Headline 1', + 'headline_part2' => 'Headline 2', + 'description' => 'Description', + 'final_urls' => [$finalUrl] + ], + 'status' => 'PAUSED' + ], + 'ad_group' => "customers/{$this->customerId}/adGroups/{$adGroupId}" + ]; - // 创建文本广告 - $textAd = new \Google\Ads\GoogleAds\V18\Resources\AdTextAdInfo([ - 'headline' => $headline, - 'description' => $description, - 'finalUrls' => $finalUrls, - ]); + // 调用 sendRequest 方法发起 POST 请求 + $response = $this->sendRequest('POST', 'ads', $data); - $ad = new \Google\Ads\GoogleAds\V18\Resources\Ad([ - 'name' => $adName, - 'adGroup' => "customers/{$customerId}/adGroups/{$adGroupId}", - 'status' => 'PAUSED', - 'textAd' => $textAd, - ]); - - $operation = new AdOperation(); - $operation->setCreate($ad); - - try { - $response = $adService->mutateAds($customerId, [$operation]); - return $response->getResults()[0]->getResourceName(); - } catch (\Google\Ads\GoogleAds\Errors\GoogleAdsException $e) { - return $e->getMessage(); + // 存储返回的广告数据到数据库 + if (isset($response['resource_name'])) { + Ad::create([ + 'name' => $response['ad']['expanded_text_ad']['headline_part1'], // 使用广告的标题字段作为示例 + 'ad_group_id' => $adGroupId, + 'status' => $response['ad']['status'], + 'final_url' => $response['ad']['expanded_text_ad']['final_urls'][0], + ]); } + + return $response; } } diff --git a/composer.json b/composer.json index 1cbd9fc..d5ea01b 100644 --- a/composer.json +++ b/composer.json @@ -27,8 +27,6 @@ "php": ">=8.1", "workerman/webman-framework": "^1.6.8", "monolog/monolog": "^2.0", - "topthink/think-orm": "^3.0", - "doctrine/dbal": "^3.9", "googleads/google-ads-php": "^25.0", "vlucas/phpdotenv": "^5.6", "guzzlehttp/guzzle": "^7.9", @@ -39,7 +37,9 @@ "webman/redis-queue": "^1.3", "webman/event": "^1.0", "workerman/crontab": "^1.0", - "illuminate/redis": "^10.48" + "illuminate/redis": "^10.48", + "symfony/var-dumper": "^6.4", + "webman/think-orm": "^1.1" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index 9b88aec..2241875 100644 --- a/composer.lock +++ b/composer.lock @@ -4,14 +4,19 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f54545ff32b44371e844061b6a25c718", + "content-hash": "95cf30222acc62ce0f01c3ff2f8696b0", "packages": [ { "name": "brick/math", "version": "0.12.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/brick/math/0.12.1/brick-math-0.12.1.zip", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", "reference": "f510c0a40911935b77b86859eb5223d58d660df1", "shasum": "" }, @@ -29,6 +34,7 @@ "Brick\\Math\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -48,30 +54,40 @@ "mathematics", "rational" ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], "time": "2023-11-29T23:19:16+00:00" }, { "name": "carbonphp/carbon-doctrine-types", - "version": "2.1.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb" + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", - "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "php": "^8.1" }, "conflict": { - "doctrine/dbal": "<3.7.0 || >=4.0.0" + "doctrine/dbal": "<4.0.0 || >=5.0.0" }, "require-dev": { - "doctrine/dbal": "^3.7.0", + "doctrine/dbal": "^4.0.0", "nesbot/carbon": "^2.71.0 || ^3.0.0", "phpunit/phpunit": "^10.3" }, @@ -101,7 +117,7 @@ ], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0" + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" }, "funding": [ { @@ -117,7 +133,7 @@ "type": "tidelift" } ], - "time": "2023-12-11T17:09:12+00:00" + "time": "2024-02-09T16:56:22+00:00" }, { "name": "doctrine/annotations", @@ -195,170 +211,17 @@ }, "time": "2024-09-05T10:15:52+00:00" }, - { - "name": "doctrine/cache", - "version": "2.2.0", - "dist": { - "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/doctrine/cache/2.2.0/doctrine-cache-2.2.0.zip", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "time": "2022-05-20T20:07:39+00:00" - }, - { - "name": "doctrine/dbal", - "version": "3.9.3", - "dist": { - "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/doctrine/dbal/3.9.3/doctrine-dbal-3.9.3.zip", - "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", - "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1|^2", - "php": "^7.4 || ^8.0", - "psr/cache": "^1|^2|^3", - "psr/log": "^1|^2|^3" - }, - "require-dev": { - "doctrine/coding-standard": "12.0.0", - "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.12.6", - "phpstan/phpstan-strict-rules": "^1.6", - "phpunit/phpunit": "9.6.20", - "psalm/plugin-phpunit": "0.18.4", - "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.10.2", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^4.4|^5.4|^6.0|^7.0", - "vimeo/psalm": "4.30.0" - }, - "suggest": { - "symfony/console": "For helpful console commands such as SQL execution and import of files." - }, - "bin": [ - "bin/doctrine-dbal" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\DBAL\\": "src" - } - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - } - ], - "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", - "homepage": "https://www.doctrine-project.org/projects/dbal.html", - "keywords": [ - "abstraction", - "database", - "db2", - "dbal", - "mariadb", - "mssql", - "mysql", - "oci8", - "oracle", - "pdo", - "pgsql", - "postgresql", - "queryobject", - "sasql", - "sql", - "sqlite", - "sqlserver", - "sqlsrv" - ], - "time": "2024-10-10T17:56:43+00:00" - }, { "name": "doctrine/deprecations", "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/doctrine/deprecations/1.1.4/doctrine-deprecations-1.1.4.zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", "shasum": "" }, @@ -381,80 +244,18 @@ "Doctrine\\Deprecations\\": "src" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.4" + }, "time": "2024-12-07T21:18:45+00:00" }, - { - "name": "doctrine/event-manager", - "version": "2.0.1", - "dist": { - "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/doctrine/event-manager/2.0.1/doctrine-event-manager-2.0.1.zip", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "conflict": { - "doctrine/common": "<2.9" - }, - "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.24" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src" - } - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/event-manager.html", - "keywords": [ - "event", - "event dispatcher", - "event manager", - "event system", - "events" - ], - "time": "2024-05-22T20:47:39+00:00" - }, { "name": "doctrine/inflector", "version": "2.0.10", @@ -627,9 +428,14 @@ { "name": "firebase/php-jwt", "version": "v6.10.2", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/firebase/php-jwt/v6.10.2/firebase-php-jwt-v6.10.2.zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/30c19ed0f3264cb660ea496895cfb6ef7ee3653b", "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b", "shasum": "" }, @@ -654,6 +460,7 @@ "Firebase\\JWT\\": "src" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], @@ -675,24 +482,34 @@ "jwt", "php" ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.10.2" + }, "time": "2024-11-24T11:22:49+00:00" }, { "name": "google/auth", - "version": "v1.44.0", + "version": "v1.45.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-auth-library-php.git", + "reference": "cfcb93162341ed5022fa976e621f0fa2b05ba6ad" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/google/auth/v1.44.0/google-auth-v1.44.0.zip", - "reference": "5670e56307d7a2eac931f677c0e59a4f8abb2e43", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/cfcb93162341ed5022fa976e621f0fa2b05ba6ad", + "reference": "cfcb93162341ed5022fa976e621f0fa2b05ba6ad", "shasum": "" }, "require": { "firebase/php-jwt": "^6.0", "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.4.5", - "php": "^8.1", + "php": "^8.0", "psr/cache": "^2.0||^3.0", - "psr/http-message": "^1.1||^2.0" + "psr/http-message": "^1.1||^2.0", + "psr/log": "^3.0" }, "require-dev": { "guzzlehttp/promises": "^2.0", @@ -714,6 +531,7 @@ "Google\\Auth\\": "src" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "Apache-2.0" ], @@ -724,14 +542,24 @@ "google", "oauth2" ], - "time": "2024-12-04T15:34:58+00:00" + "support": { + "docs": "https://googleapis.github.io/google-auth-library-php/main/", + "issues": "https://github.com/googleapis/google-auth-library-php/issues", + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.45.0" + }, + "time": "2024-12-11T02:10:48+00:00" }, { "name": "google/common-protos", "version": "4.8.3", + "source": { + "type": "git", + "url": "https://github.com/googleapis/common-protos-php.git", + "reference": "38a9a8bb459fa618da797d25d7bf36bb21d1103d" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/google/common-protos/4.8.3/google-common-protos-4.8.3.zip", + "url": "https://api.github.com/repos/googleapis/common-protos-php/zipball/38a9a8bb459fa618da797d25d7bf36bb21d1103d", "reference": "38a9a8bb459fa618da797d25d7bf36bb21d1103d", "shasum": "" }, @@ -766,6 +594,7 @@ "GPBMetadata\\Google\\Logging\\": "metadata/Logging" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "Apache-2.0" ], @@ -774,19 +603,27 @@ "keywords": [ "google" ], + "support": { + "source": "https://github.com/googleapis/common-protos-php/tree/v4.8.3" + }, "time": "2024-09-07T01:37:15+00:00" }, { "name": "google/gax", - "version": "v1.35.1", + "version": "v1.36.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/gax-php.git", + "reference": "140599cf5eae2432363ce6198e9fdff851625a7a" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/google/gax/v1.35.1/google-gax-v1.35.1.zip", - "reference": "336005867c0ca3e2ad95183cf9dd74fa67915dd9", + "url": "https://api.github.com/repos/googleapis/gax-php/zipball/140599cf5eae2432363ce6198e9fdff851625a7a", + "reference": "140599cf5eae2432363ce6198e9fdff851625a7a", "shasum": "" }, "require": { - "google/auth": "^1.34.0", + "google/auth": "^1.45", "google/common-protos": "^4.4", "google/grpc-gcp": "^0.4", "google/longrunning": "~0.4", @@ -802,7 +639,7 @@ }, "require-dev": { "phpspec/prophecy-phpunit": "^2.1", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^9.6", "squizlabs/php_codesniffer": "3.*" }, @@ -813,6 +650,7 @@ "GPBMetadata\\ApiCore\\": "metadata/ApiCore" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], @@ -821,14 +659,23 @@ "keywords": [ "google" ], - "time": "2024-12-04T15:32:12+00:00" + "support": { + "issues": "https://github.com/googleapis/gax-php/issues", + "source": "https://github.com/googleapis/gax-php/tree/v1.36.0" + }, + "time": "2024-12-11T02:47:43+00:00" }, { "name": "google/grpc-gcp", "version": "v0.4.0", + "source": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/grpc-gcp-php.git", + "reference": "2a80dbf690922aa52bb6bb79b9a32a9637a5c2d9" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/google/grpc-gcp/v0.4.0/google-grpc-gcp-v0.4.0.zip", + "url": "https://api.github.com/repos/GoogleCloudPlatform/grpc-gcp-php/zipball/2a80dbf690922aa52bb6bb79b9a32a9637a5c2d9", "reference": "2a80dbf690922aa52bb6bb79b9a32a9637a5c2d9", "shasum": "" }, @@ -852,23 +699,33 @@ "src/generated/" ] }, + "notification-url": "https://packagist.org/downloads/", "license": [ "Apache-2.0" ], "description": "gRPC GCP library for channel management", + "support": { + "issues": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/issues", + "source": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/tree/v0.4.0" + }, "time": "2024-04-03T16:37:55+00:00" }, { "name": "google/longrunning", - "version": "0.4.5", + "version": "0.4.6", + "source": { + "type": "git", + "url": "https://github.com/googleapis/php-longrunning.git", + "reference": "4eb04d47bba8095d5a47f75334b9204c2a4a7ac6" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/google/longrunning/0.4.5/google-longrunning-0.4.5.zip", - "reference": "062eab0f3b9310da9498bfe20b273f074580b916", + "url": "https://api.github.com/repos/googleapis/php-longrunning/zipball/4eb04d47bba8095d5a47f75334b9204c2a4a7ac6", + "reference": "4eb04d47bba8095d5a47f75334b9204c2a4a7ac6", "shasum": "" }, "require-dev": { - "google/gax": "^1.34.0", + "google/gax": "^1.36.0", "phpunit/phpunit": "^9.0" }, "type": "library", @@ -887,18 +744,27 @@ "GPBMetadata\\Google\\Longrunning\\": "metadata/Longrunning" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "Apache-2.0" ], "description": "Google LongRunning Client for PHP", - "time": "2024-11-16T00:28:46+00:00" + "support": { + "source": "https://github.com/googleapis/php-longrunning/tree/v0.4.6" + }, + "time": "2024-12-12T21:15:35+00:00" }, { "name": "google/protobuf", "version": "v4.29.1", + "source": { + "type": "git", + "url": "https://github.com/protocolbuffers/protobuf-php.git", + "reference": "6042b5483f8029e42473faeb8ef75ba266278381" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/google/protobuf/v4.29.1/google-protobuf-v4.29.1.zip", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/6042b5483f8029e42473faeb8ef75ba266278381", "reference": "6042b5483f8029e42473faeb8ef75ba266278381", "shasum": "" }, @@ -918,6 +784,7 @@ "GPBMetadata\\Google\\Protobuf\\": "src/GPBMetadata/Google/Protobuf" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], @@ -926,14 +793,22 @@ "keywords": [ "proto" ], + "support": { + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.1" + }, "time": "2024-12-03T22:07:45+00:00" }, { "name": "googleads/google-ads-php", "version": "v25.0.0", + "source": { + "type": "git", + "url": "https://github.com/googleads/google-ads-php.git", + "reference": "6f37249dcb0da6485cc38e74f5e3cc00e4400a4f" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/googleads/google-ads-php/v25.0.0/googleads-google-ads-php-v25.0.0.zip", + "url": "https://api.github.com/repos/googleads/google-ads-php/zipball/6f37249dcb0da6485cc38e74f5e3cc00e4400a4f", "reference": "6f37249dcb0da6485cc38e74f5e3cc00e4400a4f", "shasum": "" }, @@ -968,6 +843,7 @@ "GPBMetadata\\Google\\Ads\\GoogleAds\\": "metadata/Google/Ads/GoogleAds" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "Apache-2.0" ], @@ -979,14 +855,23 @@ ], "description": "Google Ads API client for PHP", "homepage": "https://github.com/googleads/google-ads-php", + "support": { + "issues": "https://github.com/googleads/google-ads-php/issues", + "source": "https://github.com/googleads/google-ads-php/tree/v25.0.0" + }, "time": "2024-10-18T08:01:40+00:00" }, { "name": "graham-campbell/result-type", "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/graham-campbell/result-type/v1.1.3/graham-campbell-result-type-v1.1.3.zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", "shasum": "" }, @@ -1003,6 +888,7 @@ "GrahamCampbell\\ResultType\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1021,14 +907,33 @@ "Result-Type", "result" ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], "time": "2024-07-20T21:45:45+00:00" }, { "name": "grpc/grpc", "version": "1.57.0", + "source": { + "type": "git", + "url": "https://github.com/grpc/grpc-php.git", + "reference": "b610c42022ed3a22f831439cb93802f2a4502fdf" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/grpc/grpc/1.57.0/grpc-grpc-1.57.0.zip", + "url": "https://api.github.com/repos/grpc/grpc-php/zipball/b610c42022ed3a22f831439cb93802f2a4502fdf", "reference": "b610c42022ed3a22f831439cb93802f2a4502fdf", "shasum": "" }, @@ -1048,6 +953,7 @@ "Grpc\\": "src/lib/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "Apache-2.0" ], @@ -1056,14 +962,22 @@ "keywords": [ "rpc" ], + "support": { + "source": "https://github.com/grpc/grpc-php/tree/v1.57.0" + }, "time": "2023-08-14T23:57:54+00:00" }, { "name": "guzzlehttp/guzzle", "version": "7.9.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/guzzlehttp/guzzle/7.9.2/guzzlehttp-guzzle-7.9.2.zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", "reference": "d281ed313b989f213357e3be1a179f02196ac99b", "shasum": "" }, @@ -1106,6 +1020,7 @@ "GuzzleHttp\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1158,14 +1073,37 @@ "rest", "web service" ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], "time": "2024-07-24T11:22:20+00:00" }, { "name": "guzzlehttp/promises", "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/guzzlehttp/promises/2.0.4/guzzlehttp-promises-2.0.4.zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, @@ -1188,6 +1126,7 @@ "GuzzleHttp\\Promise\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1217,14 +1156,37 @@ "keywords": [ "promise" ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/guzzlehttp/psr7/2.7.0/guzzlehttp-psr7-2.7.0.zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, @@ -1258,6 +1220,7 @@ "GuzzleHttp\\Psr7\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1309,6 +1272,24 @@ "uri", "url" ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], "time": "2024-07-18T11:15:46+00:00" }, { @@ -1695,9 +1676,14 @@ { "name": "monolog/monolog", "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "5cf826f2991858b54d5c3809bee745560a1042a7" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/monolog/monolog/2.10.0/monolog-monolog-2.10.0.zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5cf826f2991858b54d5c3809bee745560a1042a7", "reference": "5cf826f2991858b54d5c3809bee745560a1042a7", "shasum": "" }, @@ -1755,6 +1741,7 @@ "Monolog\\": "src/Monolog" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1772,6 +1759,20 @@ "logging", "psr-3" ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], "time": "2024-11-12T12:43:37+00:00" }, { @@ -1884,9 +1885,14 @@ { "name": "nikic/fast-route", "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/nikic/fast-route/v1.3.0/nikic-fast-route-v1.3.0.zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", "reference": "181d480e08d9476e61381e04a71b34dc0432e812", "shasum": "" }, @@ -1905,6 +1911,7 @@ "FastRoute\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], @@ -1919,6 +1926,10 @@ "router", "routing" ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, "time": "2018-02-13T20:26:39+00:00" }, { @@ -2097,9 +2108,14 @@ { "name": "phpoption/phpoption", "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/phpoption/phpoption/1.9.3/phpoption-phpoption-1.9.3.zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", "shasum": "" }, @@ -2125,6 +2141,7 @@ "PhpOption\\": "src/PhpOption/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "Apache-2.0" ], @@ -2147,14 +2164,33 @@ "php", "type" ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], "time": "2024-07-20T21:41:07+00:00" }, { "name": "psr/cache", "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/psr/cache/3.0.0/psr-cache-3.0.0.zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, @@ -2172,6 +2208,7 @@ "Psr\\Cache\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2187,6 +2224,9 @@ "psr", "psr-6" ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, "time": "2021-02-03T23:26:27+00:00" }, { @@ -2288,9 +2328,14 @@ { "name": "psr/http-client", "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/psr/http-client/1.0.3/psr-http-client-1.0.3.zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, @@ -2309,6 +2354,7 @@ "Psr\\Http\\Client\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2326,14 +2372,22 @@ "psr", "psr-18" ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/psr/http-factory/1.1.0/psr-http-factory-1.1.0.zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, @@ -2352,6 +2406,7 @@ "Psr\\Http\\Message\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2372,14 +2427,22 @@ "request", "response" ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/psr/http-message/2.0/psr-http-message-2.0.zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, @@ -2397,6 +2460,7 @@ "Psr\\Http\\Message\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2416,14 +2480,22 @@ "request", "response" ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, "time": "2023-04-04T09:54:51+00:00" }, { "name": "psr/log", "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/psr/log/3.0.2/psr-log-3.0.2.zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, @@ -2441,6 +2513,7 @@ "Psr\\Log\\": "src" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2457,14 +2530,22 @@ "psr", "psr-3" ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, "time": "2024-09-11T13:17:53+00:00" }, { "name": "psr/simple-cache", "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/psr/simple-cache/3.0.0/psr-simple-cache-3.0.0.zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, @@ -2482,6 +2563,7 @@ "Psr\\SimpleCache\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2499,14 +2581,22 @@ "psr-16", "simple-cache" ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, "time": "2021-10-29T13:26:27+00:00" }, { "name": "ralouphie/getallheaders", "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/ralouphie/getallheaders/3.0.3/ralouphie-getallheaders-3.0.3.zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, @@ -2523,6 +2613,7 @@ "src/getallheaders.php" ] }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2533,14 +2624,23 @@ } ], "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, "time": "2019-03-08T08:55:37+00:00" }, { "name": "ramsey/collection", "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/ramsey/collection/2.0.0/ramsey-collection-2.0.0.zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", "shasum": "" }, @@ -2583,6 +2683,7 @@ "Ramsey\\Collection\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2602,14 +2703,33 @@ "queue", "set" ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], "time": "2022-12-31T21:50:55+00:00" }, { "name": "ramsey/uuid", "version": "4.7.6", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/ramsey/uuid/4.7.6/ramsey-uuid-4.7.6.zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", "reference": "91039bc1faa45ba123c4328958e620d382ec7088", "shasum": "" }, @@ -2665,6 +2785,7 @@ "Ramsey\\Uuid\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2674,14 +2795,33 @@ "identifier", "uuid" ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.7.6" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], "time": "2024-04-27T21:32:50+00:00" }, { "name": "symfony/deprecation-contracts", "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/deprecation-contracts/v3.5.1/symfony-deprecation-contracts-v3.5.1.zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "shasum": "" }, @@ -2703,6 +2843,7 @@ "function.php" ] }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2718,14 +2859,36 @@ ], "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/polyfill-ctype", "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/polyfill-ctype/v1.31.0/symfony-polyfill-ctype-v1.31.0.zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, @@ -2753,6 +2916,7 @@ "Symfony\\Polyfill\\Ctype\\": "" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2774,14 +2938,36 @@ "polyfill", "portable" ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/polyfill-mbstring/v1.31.0/symfony-polyfill-mbstring-v1.31.0.zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, @@ -2809,6 +2995,7 @@ "Symfony\\Polyfill\\Mbstring\\": "" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2831,14 +3018,36 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/symfony/polyfill-php80/v1.31.0/symfony-polyfill-php80-v1.31.0.zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, @@ -2863,6 +3072,7 @@ "Resources/stubs" ] }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2888,6 +3098,23 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2024-09-09T11:45:10+00:00" }, { @@ -3064,11 +3291,101 @@ "time": "2024-09-25T14:20:29+00:00" }, { - "name": "topthink/think-helper", - "version": "v3.1.10", + "name": "symfony/var-dumper", + "version": "v6.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/topthink/think-helper/v3.1.10/topthink-think-helper-v3.1.10.zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", + "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "twig/twig": "^2.13|^3.0.4" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.4.15" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-08T15:28:48+00:00" + }, + { + "name": "topthink/think-helper", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "ac66cc0859a12cd5d73258f50f338aadc95e9b46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/ac66cc0859a12cd5d73258f50f338aadc95e9b46", "reference": "ac66cc0859a12cd5d73258f50f338aadc95e9b46", "shasum": "" }, @@ -3087,6 +3404,7 @@ "think\\": "src" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "Apache-2.0" ], @@ -3097,15 +3415,24 @@ } ], "description": "The ThinkPHP6 Helper Package", + "support": { + "issues": "https://github.com/top-think/think-helper/issues", + "source": "https://github.com/top-think/think-helper/tree/v3.1.10" + }, "time": "2024-11-21T01:47:51+00:00" }, { "name": "topthink/think-orm", - "version": "v3.0.31", + "version": "v3.0.32", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-orm.git", + "reference": "8d305da35664a64e4ab2a7faaaf6ed301c482651" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/topthink/think-orm/v3.0.31/topthink-think-orm-v3.0.31.zip", - "reference": "b75d534d8fa01122a7197f65c868b19f52a69fbb", + "url": "https://api.github.com/repos/top-think/think-orm/zipball/8d305da35664a64e4ab2a7faaaf6ed301c482651", + "reference": "8d305da35664a64e4ab2a7faaaf6ed301c482651", "shasum": "" }, "require": { @@ -3131,6 +3458,7 @@ "think\\": "src" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "Apache-2.0" ], @@ -3145,14 +3473,23 @@ "database", "orm" ], - "time": "2024-12-10T13:38:40+00:00" + "support": { + "issues": "https://github.com/top-think/think-orm/issues", + "source": "https://github.com/top-think/think-orm/tree/v3.0.32" + }, + "time": "2024-12-18T01:30:21+00:00" }, { "name": "vlucas/phpdotenv", "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/vlucas/phpdotenv/v5.6.1/vlucas-phpdotenv-v5.6.1.zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", "shasum": "" }, @@ -3188,6 +3525,7 @@ "Dotenv\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], @@ -3209,6 +3547,20 @@ "env", "environment" ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], "time": "2024-07-20T21:52:34+00:00" }, { @@ -3377,6 +3729,39 @@ }, "time": "2024-04-03T02:00:20+00:00" }, + { + "name": "webman/think-orm", + "version": "v1.1.6", + "source": { + "type": "git", + "url": "https://github.com/webman-php/think-orm.git", + "reference": "32b464f44e727b8a687ddd707ddb84e6f8a2ce74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/think-orm/zipball/32b464f44e727b8a687ddd707ddb84e6f8a2ce74", + "reference": "32b464f44e727b8a687ddd707ddb84e6f8a2ce74", + "shasum": "" + }, + "require": { + "topthink/think-orm": "^2.0.53 || ^3.0.0 || ^4.0.0 || dev-master" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\ThinkOrm\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "support": { + "issues": "https://github.com/webman-php/think-orm/issues", + "source": "https://github.com/webman-php/think-orm/tree/v1.1.6" + }, + "time": "2024-11-22T03:17:44+00:00" + }, { "name": "workerman/crontab", "version": "v1.0.6", @@ -3501,11 +3886,16 @@ }, { "name": "workerman/webman-framework", - "version": "v1.6.8", + "version": "v1.6.9", + "source": { + "type": "git", + "url": "https://github.com/walkor/webman-framework.git", + "reference": "c328d94f3aa50a06a59689656a24a580a7b51dc1" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/workerman/webman-framework/v1.6.8/workerman-webman-framework-v1.6.8.zip", - "reference": "7e1d28d266b2c9aa69b2ca8f4e3c1da778e2ad4a", + "url": "https://api.github.com/repos/walkor/webman-framework/zipball/c328d94f3aa50a06a59689656a24a580a7b51dc1", + "reference": "c328d94f3aa50a06a59689656a24a580a7b51dc1", "shasum": "" }, "require": { @@ -3532,6 +3922,7 @@ "Support\\Exception\\": "./src/support/exception" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -3549,14 +3940,26 @@ "High Performance", "http service" ], - "time": "2024-12-10T07:13:15+00:00" + "support": { + "email": "walkor@workerman.net", + "forum": "https://wenda.workerman.net/", + "issues": "https://github.com/walkor/webman/issues", + "source": "https://github.com/walkor/webman-framework", + "wiki": "https://doc.workerman.net/" + }, + "time": "2024-12-11T12:41:18+00:00" }, { "name": "workerman/workerman", "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "cafb5a43d93d7d30a16b32a57948581cca993562" + }, "dist": { "type": "zip", - "url": "https://mirrors.cloud.tencent.com/repository/composer/workerman/workerman/v4.2.1/workerman-workerman-v4.2.1.zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/cafb5a43d93d7d30a16b32a57948581cca993562", "reference": "cafb5a43d93d7d30a16b32a57948581cca993562", "shasum": "" }, @@ -3572,6 +3975,7 @@ "Workerman\\": "./" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -3589,6 +3993,23 @@ "asynchronous", "event-loop" ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://wenda.workerman.net/", + "issues": "https://github.com/walkor/workerman/issues", + "source": "https://github.com/walkor/workerman", + "wiki": "http://doc.workerman.net/" + }, + "funding": [ + { + "url": "https://opencollective.com/workerman", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], "time": "2024-11-24T11:45:37+00:00" } ], diff --git a/config/bootstrap.php b/config/bootstrap.php index 44054e0..1782d83 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -14,5 +14,7 @@ return [ support\bootstrap\Session::class, - support\bootstrap\LaravelDb::class, + // support\bootstrap\LaravelDb::class, +// Webman\ThinkCache\ThinkCache::class, + Webman\ThinkOrm\ThinkOrm::class, ]; diff --git a/config/database.php b/config/database.php index d821249..7232749 100644 --- a/config/database.php +++ b/config/database.php @@ -14,38 +14,38 @@ return [ // 默认数据库 - 'default' => 'pgsql', - - // 各种数据库配置 - 'connections' => [ - 'mysql' => [ - 'driver' => 'mysql', - 'host' => getenv('DB_HOST'), - 'port' => getenv('DB_PORT'), - 'database' => getenv('DB_NAME'), - 'username' => getenv('DB_USER'), - 'password' => getenv('DB_PASSWORD'), - 'unix_socket' => '', - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', - 'prefix' => '', - 'strict' => true, - 'engine' => null, - 'options' => [ - \PDO::ATTR_TIMEOUT => 3 - ] - ], - 'pgsql' => [ - 'driver' => getenv('DB_PG_CONNECTION'), - 'hostname' => getenv('DB_PG_HOST'), - 'hostport' => getenv('DB_PG_PORT'), - 'database' => getenv('DB_PG_NAME'), - 'username' => getenv('DB_PG_USER'), - 'password' => getenv('DB_PG_PASSWORD'), - 'unix_socket' => '', - 'charset' => 'utf8', - 'prefix' => 'ad_', // 数据表前缀 - 'debug' => true, - ] - ], +// 'default' => 'mysql', +// +// // 各种数据库配置 +// 'connections' => [ +// 'mysql' => [ +// 'driver' => 'mysql', +// 'host' => getenv('DB_HOST'), +// 'port' => getenv('DB_PORT'), +// 'database' => getenv('DB_NAME'), +// 'username' => getenv('DB_USER'), +// 'password' => getenv('DB_PASSWORD'), +// 'unix_socket' => '', +// 'charset' => 'utf8mb4', +// 'collation' => 'utf8mb4_unicode_ci', +// 'prefix' => '', +// 'strict' => true, +// 'engine' => null, +// 'options' => [ +// \PDO::ATTR_TIMEOUT => 3 +// ] +// ], +// 'pgsql' => [ +// 'driver' => getenv('DB_PG_CONNECTION'), +// 'hostname' => getenv('DB_PG_HOST'), +// 'hostport' => getenv('DB_PG_PORT'), +// 'database' => getenv('DB_PG_NAME'), +// 'username' => getenv('DB_PG_USER'), +// 'password' => getenv('DB_PG_PASSWORD'), +// 'unix_socket' => '', +// 'charset' => 'utf8', +// 'prefix' => 'ad_', // 数据表前缀 +// 'debug' => true, +// ] +// ], ]; diff --git a/config/event.php b/config/event.php index afbf648..962ef2a 100644 --- a/config/event.php +++ b/config/event.php @@ -3,15 +3,32 @@ use app\event\TiktokAds; use app\event\TiktokAdsDetails; +use app\event\GoogleAdsCampaigns; +use app\event\GoogleAdsGroups; +use app\event\GoogleAdsAds; +use app\event\GoogleAdsDateDatas; return [ //知乎热榜 - TiktokAds::type => [ - [TiktokAds::class, 'update'], +// TiktokAds::type => [ +// [TiktokAds::class, 'update'], +// ], +// TiktokAdsDetails::type => [ +// [TiktokAdsDetails::class, 'update'], +// ], + GoogleAdsCampaigns::type => [ + [GoogleAdsCampaigns::class, 'getCampaigns'], ], - TiktokAdsDetails::type => [ - [TiktokAdsDetails::class, 'update'], + GoogleAdsDateDatas::type => [ + [GoogleAdsDateDatas::class, 'getDateDatas'], + ], + GoogleAdsGroups::type => [ + [GoogleAdsGroups::class, 'getGroups'], + ], + GoogleAdsAds::type => [ + [GoogleAdsAds::class, 'getAds'], ], + ]; diff --git a/config/google_ads_php.ini b/config/google_ads_php.ini index 3d28727..6f7ec7e 100644 --- a/config/google_ads_php.ini +++ b/config/google_ads_php.ini @@ -32,7 +32,7 @@ loginCustomerId = 1509096882 ; For installed application flow. clientId = "117429539543-t73vtg7v1vag5b2dg68qaaaj00gmacjs.apps.googleusercontent.com" clientSecret = "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" -refreshToken = "1//0ei7JnUPbgXopCgYIARAAGA4SNwF-L9Irma-BXlnDqEtrklnBXNK13e915TyQsEXbinSW3v0ZsKYB7C7-bYe2xG7osPLQ5xzeqo8" +refreshToken = "1//0eNpv3UrnIRMaCgYIARAAGA4SNwF-L9Ir_W9Fs5CrdW_7IFjRkq2nA6TZwSyi9Y8ukj8nirWt3BvOR74j3HatYX3cug7vfzGPUhs" ; For service account flow. ; jsonKeyFilePath = "INSERT_ABSOLUTE_PATH_TO_OAUTH2_JSON_KEY_FILE_HERE" diff --git a/config/route.php b/config/route.php index 3885bff..e607964 100644 --- a/config/route.php +++ b/config/route.php @@ -32,6 +32,7 @@ Route::group('/googleads', function () { }); Route::group('/campaign', function () { Route::post('/list', [GoogleAdsController::class, 'listCampaigns']); +// Route::post('/list', [GoogleAdsController::class, 'listCampaignsNext']); }); Route::group('/campaign', function () { Route::post('/update', [GoogleAdsController::class, 'updateCampaign']); @@ -48,8 +49,12 @@ Route::group('/googleads', function () { Route::group('/group', function () { Route::post('/update', [GoogleAdsController::class, 'updateGroup']); }); + Route::group('/group', function () { + Route::post('/list', [GoogleAdsController::class, 'listGroups']); + }); Route::group('/ad', function () { Route::post('/update', [GoogleAdsController::class, 'updateAd']); + Route::post('/list', [GoogleAdsController::class, 'listAds']); Route::group('/responsive_search', function () { Route::post('/list', [GoogleAdsController::class, 'listResponsiveSearchAds']); Route::post('/update', [GoogleAdsController::class, 'updateResponsiveSearchAd']); @@ -65,6 +70,9 @@ Route::group('/googleads', function () { Route::group('/account_link', function () { Route::post('/list', [GoogleAdsController::class, 'accessibleCustomers']); }); + Route::group('/datedata', function () { + Route::post('/list', [GoogleAdsController::class, 'listDateDatas']); + }); }); diff --git a/config/thinkorm.php b/config/thinkorm.php new file mode 100644 index 0000000..0d74f13 --- /dev/null +++ b/config/thinkorm.php @@ -0,0 +1,63 @@ + 'pgsql', + 'connections' => [ + 'mysql' => [ + // 数据库类型 + 'type' => 'mysql', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => 'test', + // 数据库用户名 + 'username' => 'root', + // 数据库密码 + 'password' => '123456', + // 数据库连接端口 + 'hostport' => '3306', + // 数据库连接参数 + 'params' => [ + // 连接超时3秒 + \PDO::ATTR_TIMEOUT => 3, + ], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 断线重连 + 'break_reconnect' => true, + // 自定义分页类 + 'bootstrap' => '' + ], + 'pgsql' => [ + // 数据库类型 + 'type' => 'pgsql', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => 'toolbox', + // 数据库用户名 + 'username' => 'toolbox', + // 数据库密码 + 'password' => 'hope8848', + // 数据库连接端口 + 'hostport' => '5432', + // 数据库连接参数 + 'params' => [ + // 连接超时3秒 + \PDO::ATTR_TIMEOUT => 3, + ], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 断线重连 + 'break_reconnect' => true, + // 自定义分页类 + 'bootstrap' => '', +// 'schema' => 'public'// 设置默认 schema + 'debug' => true, + ], + ], +];