初始化采集任务

This commit is contained in:
hgc 2024-12-19 20:38:45 +08:00
parent a49551c73e
commit 781d4792e8
19 changed files with 2903 additions and 457 deletions

View File

@ -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

280
app/event/GoogleAdsAds.php Normal file
View File

@ -0,0 +1,280 @@
<?php
namespace app\event;
use app\service\GoogleAdsAdService;
use Google\ApiCore\ApiException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use support\Db;
use support\Request;
use support\Response;
use DI\Annotation\Inject;
//use QL\QueryList;
use support\Redis;
class GoogleAdsAds
{
/**
* @Inject
* @var GoogleAdsAdService
*/
private $googleAdsAdService;
//微博热榜地址
// const url = 'https://library.tiktok.com/api/v1/search?region=GB&type=1&start_time=1666540800&end_time=1666627200';
const userAgent = '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';
const type = 'googleadsads';
const limit = 12;
const sort_order = 'impression,desc';
// const countries = ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IS", "IE","IT", "LV", "LI", "LT", "LU", "MT", "NL", "NO", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "CH", "TR", "GB"] ;
const countries = ["GB", "BE"];
public function listAds(Request $request)
{
$options = $request->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
]);
}
}

View File

@ -0,0 +1,281 @@
<?php
namespace app\event;
use app\model\Campaign;
use app\service\GoogleAdsCampaignService;
use Google\ApiCore\ApiException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use support\Db;
use support\Request;
use support\Response;
use DI\Annotation\Inject;
//use QL\QueryList;
use support\Redis;
class GoogleAdsCampaigns
{
/**
* @Inject
* @var GoogleAdsCampaignService
*/
private $googleAdsCampaignService;
//微博热榜地址
// const url = 'https://library.tiktok.com/api/v1/search?region=GB&type=1&start_time=1666540800&end_time=1666627200';
const userAgent = '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';
const type = 'googleadscampaigns';
const limit = 12;
const sort_order = 'impression,desc';
// const countries = ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IS", "IE","IT", "LV", "LI", "LT", "LU", "MT", "NL", "NO", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "CH", "TR", "GB"] ;
const countries = ["GB", "BE"];
public function listCampaigns(Request $request)
{
$options = $request->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
]);
}
}

View File

@ -0,0 +1,280 @@
<?php
namespace app\event;
use app\model\Campaign;
use app\service\GoogleAdsCampaignService;
use Google\ApiCore\ApiException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use support\Db;
use support\Request;
use support\Response;
use DI\Annotation\Inject;
//use QL\QueryList;
use support\Redis;
class GoogleAdsDateDatas
{
/**
* @Inject
* @var GoogleAdsCampaignService
*/
private $googleAdsCampaignService;
//微博热榜地址
// const url = 'https://library.tiktok.com/api/v1/search?region=GB&type=1&start_time=1666540800&end_time=1666627200';
// const userAgent = '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';
const type = 'googleadsgdatedatas';
const limit = 12;
const sort_order = 'impression,desc';
// const countries = ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IS", "IE","IT", "LV", "LI", "LT", "LU", "MT", "NL", "NO", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "CH", "TR", "GB"] ;
const countries = ["GB", "BE"];
public function listCampaigns(Request $request)
{
$options = $request->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
]);
}
}

View File

@ -0,0 +1,281 @@
<?php
namespace app\event;
use app\model\AdGroup;
use app\service\GoogleAdsCampaignService;
use app\service\GoogleAdsGroupService;use Google\ApiCore\ApiException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use support\Db;
use support\Request;
use support\Response;
use DI\Annotation\Inject;
//use QL\QueryList;
use support\Redis;
class GoogleAdsGroups
{
/**
* @Inject
* @var GoogleAdsGroupService
*/
private $googleAdsGroupService;
//微博热榜地址
// const url = 'https://library.tiktok.com/api/v1/search?region=GB&type=1&start_time=1666540800&end_time=1666627200';
const userAgent = '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';
const type = 'googleadsgroups';
const limit = 12;
const sort_order = 'impression,desc';
// const countries = ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IS", "IE","IT", "LV", "LI", "LT", "LU", "MT", "NL", "NO", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "CH", "TR", "GB"] ;
const countries = ["GB", "BE"];
public function listGroups(Request $request)
{
$options = $request->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
]);
}
}

View File

@ -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分钟执行一次

View File

@ -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

View File

@ -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,

View File

@ -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.

View File

@ -0,0 +1,136 @@
<?php
namespace app\service;
use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Lib\V18\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V18\GoogleAdsClientBuilder;
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;
class GoogleAdsSdkService
{
protected $googleAdsClient;
public function __construct()
{
// 使用 GoogleAdsClientBuilder 构建 GoogleAdsClient 实例
$oAuth2Credential = (new OAuth2TokenBuilder())->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();
}
}
}

View File

@ -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;
}
}

View File

@ -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. "

1007
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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,
];

View File

@ -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,
// ]
// ],
];

View File

@ -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'],
],
];

View File

@ -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"

View File

@ -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']);
});
});

63
config/thinkorm.php Normal file
View File

@ -0,0 +1,63 @@
<?php
return [
'default' => '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,
],
],
];