谷歌创意素材

This commit is contained in:
hgc 2024-12-26 21:13:15 +08:00
parent 2033c3c46c
commit d1713a5189
16 changed files with 1471 additions and 40 deletions

View File

@ -0,0 +1,59 @@
<?php
namespace app\command;
use app\model\GoogleAdsAsset;
use app\model\GoogleAdsAd;
use app\model\GoogleAdsAssetRelations;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Db;
class SyncGoogleAdsAsset extends Command
{
protected function configure()
{
$this->setName('sync:google_ads_asset')
->setDescription('Synchronize Google Ads Asset relations');
}
protected function execute(Input $input, Output $output)
{
// 获取所有素材
$assets = GoogleAdsAsset::where('status', 1)->select();
foreach ($assets as $asset) {
// 获取广告表中的所有广告
$ads = GoogleAdsAd::where('status', 1)->select();
foreach ($ads as $ad) {
// 检查广告的 metadata 是否包含素材的 resource_name
if (isset($ad->metadata) && strpos($ad->metadata, $asset->resource_name) !== false) {
// 插入关联记录
$existingRelation = GoogleAdsAssetRelations::where('asset_id', $asset->asset_id)
->where('ad_id', $ad->ad_id)
->where('date', date('Y-m-d'))
->find();
if (!$existingRelation) {
// 如果没有现有记录,则插入新的关联
GoogleAdsAssetRelations::create([
'asset_id' => $asset->asset_id,
'ad_id' => $ad->ad_id,
'ad_group_id' => $ad->ad_group_id,
'campaign_id' => $ad->campaign_id,
'date' => date('Y-m-d')
]);
$output->writeln("Inserted relation for Asset ID: {$asset->asset_id} and Ad ID: {$ad->ad_id}");
} else {
$output->writeln("Relation already exists for Asset ID: {$asset->asset_id} and Ad ID: {$ad->ad_id}");
}
}
}
}
$output->writeln('Google Ads Asset synchronization completed.');
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace app\controller;
use app\command\SyncGoogleAdsAsset;
use think\facade\Console;
use think\Request;
class AssetController
{
public function sync(Request $request)
{
// 执行命令行任务
$result = Console::call('sync:google_ads_asset');
return json(['message' => 'Synchronization completed successfully']);
}
}

View File

@ -9,6 +9,7 @@ use support\Request;
use app\service\GoogleAdsCampaignService;
use app\service\GoogleAdsGroupService;
use app\service\GoogleAdsAdService;
use app\service\GoogleAdsAssetService;
use app\service\GoogleAdsAccountService;
use support\Response;
use DI\Annotation\Inject;
@ -35,6 +36,12 @@ class GoogleAdsController
*/
private $googleAdsAdService;
/**
* @Inject
* @var GoogleAdsAssetService
*/
private $googleAdsAssetService;
/**
* @Inject
* @var GoogleAdsAccountService
@ -79,6 +86,14 @@ class GoogleAdsController
return $this->getAds($options);
}
public function listAssets(Request $request)
{
$options = $request->all();
// 继续处理 Google Ads API 操作
return $this->getAssets($options);
}
public function listDateDatas(Request $request)
{
$options = $request->all();
@ -254,6 +269,17 @@ class GoogleAdsController
return $this->successResponse(['groups_list' => $resourceName]);
}
/**
* get assets
* @throws ApiException
*/
public function getAssets($options): Response
{
$resourceName = $this->googleAdsAssetService->runListAssets($options['customer_id']);
// return $this->successResponse(['assets_list' => $resourceName]);
return $this->successResponse(['assets_list' => 'succeed added']);
}
/**
* get date datas
* @throws ApiException

View File

@ -11,6 +11,7 @@ use app\event\GoogleAdsCampaigns;
use app\event\GoogleAdsGroups;
use app\event\GoogleAdsAds;
use app\event\GoogleAdsDateDatas;
use app\event\GoogleAdsMaterials;
use Webman\Event\Event;
class OAuthController
@ -120,8 +121,10 @@ class OAuthController
// Event::emit(GoogleAdsCampaigns::type, []);
// dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsGroups::type . '开始');
// Event::emit(GoogleAdsGroups::type, []);
dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAds::type . '开始');
Event::emit(GoogleAdsAds::type, []);
// dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAds::type . '开始');
// Event::emit(GoogleAdsAdMaterials::type, []);
dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsMaterials::type . '开始');
Event::emit(GoogleAdsMaterials::type, []);
return $this->successResponse(['data' => []]);

View File

@ -0,0 +1,282 @@
<?php
namespace app\event;
use app\service\GoogleAdsAssetService;
use app\service\GoogleOAuthService;
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 GoogleAdsAssets
{
/**
* @Inject
* @var GoogleOAuthService
*/
private $googleOAuthService;
//微博热榜地址
// 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 = 'googleadsassets';
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 listAssets(Request $request)
{
$options = $request->all();
// 继续处理 Google Ads API 操作
return $this->getAssets($options);
}
/**
* get assets
* @throws ApiException
*/
public function getAssets($options)
{
$customers = $this->googleOAuthService->getGoogleAdCustomers([]);
foreach ($customers as $customerId) {
$googleAdsAssetService = new GoogleAdsAssetService($customerId);
$resourceName = $googleAdsAssetService->runListAssets($customerId);
}
// return $this->successResponse(['ads_list' => $resourceName]);
}
/**
* 每天爬取tiktok广告
* @return void
*/
public function update()
{
try {
$client = new Client([
//允许重定向
// 'allow_redirects' => true,
]);
// 获取前两天 0 点的时间戳
$dayBeforeYesterdayStart = strtotime('-2 days 00:00:00');
// 获取前一天 0 点的时间戳
$yesterdayStart = strtotime('-1 day 00:00:00');
$countryCache = Redis::get(self::type . 'lastCountry');
//全部跑完跳出
if ($countryCache == 'All') {
dump($countryCache . '国家更新tiktok Ads 完成');
return;
}
if (empty($countryCache)) {
$countryCache = 'GB';
}
$searchIdCache = Redis::get(self::type . 'lastSearchId');
$offsetCache = Redis::get(self::type . 'nextOffset');
$totalCache = Redis::get(self::type . 'totalCache');
if (!isset($searchIdCache) || empty($searchIdCache)) {
$searchIdCache = '';
}
if (!isset($offsetCache) || empty($searchIdCache)) {
$offsetCache = 0;
}
if (!isset($totalCache) || empty($searchIdCache)) {
$totalCache = 0;
}
//判断国家是否跑完。
if ($totalCache > 0 && $offsetCache > ceil($totalCache / self::limit)) {
$key = array_search($countryCache, self::countries, true);
if (in_array($countryCache, self::countries) && isset(self::countries[$key + 1])) {
$countryCache = self::countries[$key + 1];
dump('更新' . $countryCache . '国家的tiktok Ads 完成');
} else {
$countryCache = 'All'; //赋值非正常国家的code中断定时任务
dump($countryCache . '国家更新tiktok Ads 完成');
}
return;
}
$start_time = $dayBeforeYesterdayStart;
$end_time = $yesterdayStart;
//当前国家爬取
$currentParams = null;
$currentParams = ['country' => $countryCache, 'search_id' => $searchIdCache, 'type' => 1, 'start_time' => $start_time, 'end_time' => $end_time];
$url = 'https://library.tiktok.com/api/v1/search?region=' . $currentParams['country'] . '&type=' . $currentParams['type'] . '&start_time=' . $currentParams['start_time'] . '&end_time=' . $currentParams['end_time'];
$res = json_decode($client->post($url, [
'headers' => [
'accept' => 'application/json, text/plain, */*',
'accept-language' => 'zh-CN,zh;q=0.9',
'content-type' => 'application/json',
'cookie' => 'cookie: _ttp=2ov8Fc4C2CaNscHJd90O9fMhlpE; _ga=GA1.1.1025820618.1731926196; FPID=FPID2.2.Bcgkp%2Fk%2Bbn5w5YeSMR9wd9VpNHJwTUpkkaEqSdCEa0w%3D.1731926196; FPAU=1.2.944915349.1731926193; FPLC=mbVyryI5aG6IVpAvhs1JsgWjA7FVA6QsCJ7VbXhM7zWoXNp4rcD0IK7FNTTf%2FuOrqeOgqEhTd4NB3hY7q3aDVTGQa3WGHqxkGte4%2BBZxsrpaHFas9kb7DPRXM12T5Q%3D%3D; _ga_TEQXTT9FE4=GS1.1.1732097542.7.0.1732097542.0.0.857840528',
'origin' => 'https://library.tiktok.com',
'priority' => 'u=1, i',
'referer' => 'https://library.tiktok.com/ads?region=AT&start_time=1731945600000&end_time=1732032000000&adv_name=&adv_biz_ids=&query_type=&sort_type=last_shown_date,desc',
'sec-ch-ua' => '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
'sec-ch-ua-mobile' => '?0',
'sec-ch-ua-platform' => '"Windows"',
'sec-fetch-dest' => 'empty',
'sec-fetch-mode' => 'cors',
'sec-fetch-site' => 'same-origin',
'user-agent' => self::userAgent,
// 'user-agent' => 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
],
'json' => [
'query' => '',
'query_type' => '',
'adv_biz_ids' => '',
'order' => self::sort_order,
'offset' => (int)$offsetCache,
'search_id' => $currentParams['search_id'],
'limit' => self::limit,
],
])->getBody()->getContents(), true);
// dump($res);return; //调试点
if ($res['search_id'] != $searchIdCache) {
$searchIdCache = $res['search_id'];
dump('search_id更新 ' . $searchIdCache . ' 成功');
}
if ($res['total'] == 0 || $res['code'] != 0) {
dump('更新tiktok Ads接口响应异常' . json_encode($res, JSON_UNESCAPED_UNICODE));
return;
}
$listAdsIds = [];
foreach ($res['data'] as $ad) {
if ($ad['audit_status'] == 2 || empty($ad['videos'])) {
continue; //审核不过或者没视频不采集
}
$imagesJson = is_array($ad['image_urls']) ? json_encode($ad['image_urls']) : json_encode([]);
$rejection_info = is_array($ad['rejection_info']) ? json_encode($ad['rejection_info']) : null;
// dump($rejection_info);
$insertData[$ad['id']] = [
'ad_id' => $ad['id'],
'name' => $ad['name'],
'audit_status' => $ad['audit_status'],
'type' => $ad['type'],
'first_shown_date' => $ad['first_shown_date'],
'last_shown_date' => $ad['last_shown_date'],
'image_urls' => $imagesJson,
'estimated_audience' => $ad['estimated_audience'],
'spent' => $ad['spent'],
'impression' => $ad['impression'],
'show_mode' => $ad['show_mode'],
'rejection_info' => $rejection_info,
'sor_audit_status' => $ad['sor_audit_status'],
'country_code' => $countryCache,
];
if (isset($ad['videos']) && !empty($ad['videos'])) {
// 遍历 "videos" 数组
foreach ($ad['videos'] as $video) {
$insertData[$ad['id']]['video_url'] = $video['video_url'];
$insertData[$ad['id']]['cover_img'] = $video['cover_img'];
}
}
$listAdsIds = array_column($insertData, 'ad_id');
}
// dump($insertData);return;
if (empty($insertData)) return;
//开启事务
Db::beginTransaction();
//删除原来的旧数据
TiktokAd::query()->whereIn('ad_id', array_keys($insertData))->delete();
//添加新的数据
TiktokAd::query()->insert($insertData);
//redis缓存
Redis::set(self::type, json_encode($insertData, JSON_UNESCAPED_UNICODE));
//redis缓存 记录更新时间
$time = date('Y-m-d H:i:s');
Redis::set(self::type . 'time', $time);
Redis::set(self::type . 'lastCountry', $countryCache);
Redis::set(self::type . 'nextOffset', ++$offsetCache); //记录下一次的offset
Redis::set(self::type . 'totalCache', $res['total']);
Redis::set(self::type . 'lastSearchId', $searchIdCache);
if (!empty($listAdsIds)) {
Redis::rPush(self::type . 'AdsIds', ...$listAdsIds);
}
//提交事务
Db::commit();
//销毁$res
unset($res);
dump(date('Y-m-d H:i:s') . '更新' . self::type . '成功');
} catch (GuzzleException|\Exception $exception) {
//回滚事务
Db::rollBack();
dump('更' . self::type . '异常:' . $exception->getMessage());
dump($exception);
}
// } catch (ClientExceptionInterface $e) {
// // 捕获 4xx 错误
// dump( 'Client error: ' . $e->getMessage() . "\n");
// } catch (ServerExceptionInterface $e) {
// // 捕获 5xx 错误
// dump('Server error: ' . $e->getMessage() . "\n");
// } catch (TransportExceptionInterface $e) {
// // 捕获网络传输错误
// dump('Transport error: ' . $e->getMessage() . "\n") ;
// } catch (\Exception $e) {
// // 捕获所有其他错误
// dump('General error: ' . $e->getMessage() . "\n") ;
// }
}
// 可以加入一些公共方法
protected function successResponse($data): Response
{
return Json([
'code' => 0,
'msg' => 'ok',
'data' => $data,
]);
}
protected function errorResponse($code, $message, $data = []): Response
{
return Json([
'code' => $code,
'msg' => $message ?: 'error',
'data' => $data
]);
}
}

View File

@ -30,7 +30,7 @@ class Ad extends Model
'status' => 1, // 广告状态默认值为 'ENABLED'
];
// 状态判断常量
// 状态判断常量
const STATUS_UNSPECIFIED = 0;
const STATUS_UNKNOWN = 1;
const STATUS_ENABLED = 2;
@ -40,7 +40,7 @@ class Ad extends Model
// 获取广告状态
public function getStatusTextAttr($value, $data)
{
$statusMap = [
$statusMap = [
self::STATUS_UNSPECIFIED => 'UNSPECIFIED',
self::STATUS_UNKNOWN => 'UNKNOWN',
self::STATUS_ENABLED => 'ENABLED',
@ -50,6 +50,97 @@ class Ad extends Model
return $statusMap[$data['status']] ?? 'UNKNOWN';
}
// 定义广告类型枚举常量
const UNSPECIFIED = 0;
const UNKNOWN = 1;
const TEXT_AD = 2;
const EXPANDED_TEXT_AD = 3;
const EXPANDED_DYNAMIC_SEARCH_AD = 7;
const HOTEL_AD = 8;
const SHOPPING_SMART_AD = 9;
const SHOPPING_PRODUCT_AD = 10;
const VIDEO_AD = 12;
const IMAGE_AD = 14;
const RESPONSIVE_SEARCH_AD = 15;
const LEGACY_RESPONSIVE_DISPLAY_AD = 16;
const APP_AD = 17;
const LEGACY_APP_INSTALL_AD = 18;
const RESPONSIVE_DISPLAY_AD = 19;
const LOCAL_AD = 20;
const HTML5_UPLOAD_AD = 21;
const DYNAMIC_HTML5_AD = 22;
const APP_ENGAGEMENT_AD = 23;
const SHOPPING_COMPARISON_LISTING_AD = 24;
const VIDEO_BUMPER_AD = 25;
const VIDEO_NON_SKIPPABLE_IN_STREAM_AD = 26;
const VIDEO_OUTSTREAM_AD = 27;
const VIDEO_TRUEVIEW_IN_STREAM_AD = 29;
const VIDEO_RESPONSIVE_AD = 30;
const SMART_CAMPAIGN_AD = 31;
const CALL_AD = 32;
const APP_PRE_REGISTRATION_AD = 33;
const IN_FEED_VIDEO_AD = 34;
const DEMAND_GEN_MULTI_ASSET_AD = 40;
const DEMAND_GEN_CAROUSEL_AD = 41;
const TRAVEL_AD = 37;
const DEMAND_GEN_VIDEO_RESPONSIVE_AD = 42;
const DEMAND_GEN_PRODUCT_AD = 39;
private static $valueToName = [
self::UNSPECIFIED => 'UNSPECIFIED',
self::UNKNOWN => 'UNKNOWN',
self::TEXT_AD => 'TEXT_AD',
self::EXPANDED_TEXT_AD => 'EXPANDED_TEXT_AD',
self::EXPANDED_DYNAMIC_SEARCH_AD => 'EXPANDED_DYNAMIC_SEARCH_AD',
self::HOTEL_AD => 'HOTEL_AD',
self::SHOPPING_SMART_AD => 'SHOPPING_SMART_AD',
self::SHOPPING_PRODUCT_AD => 'SHOPPING_PRODUCT_AD',
self::VIDEO_AD => 'VIDEO_AD',
self::IMAGE_AD => 'IMAGE_AD',
self::RESPONSIVE_SEARCH_AD => 'RESPONSIVE_SEARCH_AD',
self::LEGACY_RESPONSIVE_DISPLAY_AD => 'LEGACY_RESPONSIVE_DISPLAY_AD',
self::APP_AD => 'APP_AD',
self::LEGACY_APP_INSTALL_AD => 'LEGACY_APP_INSTALL_AD',
self::RESPONSIVE_DISPLAY_AD => 'RESPONSIVE_DISPLAY_AD',
self::LOCAL_AD => 'LOCAL_AD',
self::HTML5_UPLOAD_AD => 'HTML5_UPLOAD_AD',
self::DYNAMIC_HTML5_AD => 'DYNAMIC_HTML5_AD',
self::APP_ENGAGEMENT_AD => 'APP_ENGAGEMENT_AD',
self::SHOPPING_COMPARISON_LISTING_AD => 'SHOPPING_COMPARISON_LISTING_AD',
self::VIDEO_BUMPER_AD => 'VIDEO_BUMPER_AD',
self::VIDEO_NON_SKIPPABLE_IN_STREAM_AD => 'VIDEO_NON_SKIPPABLE_IN_STREAM_AD',
self::VIDEO_OUTSTREAM_AD => 'VIDEO_OUTSTREAM_AD',
self::VIDEO_TRUEVIEW_IN_STREAM_AD => 'VIDEO_TRUEVIEW_IN_STREAM_AD',
self::VIDEO_RESPONSIVE_AD => 'VIDEO_RESPONSIVE_AD',
self::SMART_CAMPAIGN_AD => 'SMART_CAMPAIGN_AD',
self::CALL_AD => 'CALL_AD',
self::APP_PRE_REGISTRATION_AD => 'APP_PRE_REGISTRATION_AD',
self::IN_FEED_VIDEO_AD => 'IN_FEED_VIDEO_AD',
self::DEMAND_GEN_MULTI_ASSET_AD => 'DEMAND_GEN_MULTI_ASSET_AD',
self::DEMAND_GEN_CAROUSEL_AD => 'DEMAND_GEN_CAROUSEL_AD',
self::TRAVEL_AD => 'TRAVEL_AD',
self::DEMAND_GEN_VIDEO_RESPONSIVE_AD => 'DEMAND_GEN_VIDEO_RESPONSIVE_AD',
self::DEMAND_GEN_PRODUCT_AD => 'DEMAND_GEN_PRODUCT_AD',
];
// 检查广告类型是否有效
public static function isValidAdType($adType) {
return in_array($adType, array_keys(self::$valueToName));
}
// 获取广告类型名称
public static function getAdTypeName($adType) {
return self::$valueToName[$adType] ?? 'UNKNOWN';
}
// 在模型中使用这些常量
public function setAdType($adType) {
if (!self::isValidAdType($adType)) {
throw new Exception("Invalid ad type");
}
$this->ad_type = $adType;
}
// 更新广告状态
// public function updateStatus($status)
// {
@ -84,6 +175,12 @@ class Ad extends Model
return $this->belongsTo(AdGroup::class, 'ad_group_id', 'ad_group_id');
}
// 关联到素材关系
public function assetRelations()
{
return $this->hasMany(AssetRelation::class, 'ad_id', 'ad_id');
}
// 关联 Customer 模型(广告属于客户)
// public function customer()
// {

View File

@ -32,16 +32,17 @@ class AdGroup extends Model
'cpc_bid_micros' => 0, // 每次点击出价默认值为0
];
// 状态判断常量
// 状态判断常量
const STATUS_UNSPECIFIED = 0;
const STATUS_UNKNOWN = 1;
const STATUS_ENABLED = 2;
const STATUS_PAUSED = 3;
const STATUS_REMOVED = 4;
// 获取广告组状态
public function getStatusTextAttr($value, $data)
{
$statusMap = [
$statusMap = [
self::STATUS_UNSPECIFIED => 'UNSPECIFIED',
self::STATUS_UNKNOWN => 'UNKNOWN',
self::STATUS_ENABLED => 'ENABLED',
@ -96,5 +97,11 @@ class AdGroup extends Model
{
return $this->hasMany(Ad::class, 'ad_group_id', 'ad_group_id');
}
// 关联到素材关系
public function assetRelations()
{
return $this->hasMany(AssetRelation::class, 'ad_group_id', 'ad_group_id');
}
}

56
app/model/Asset.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace app\model;
use think\Model;
class Asset extends Model
{
// 设置表名
protected $table = 'bps.bps_google_ads_asset';
// 设置主键
protected $pk = 'asset_id';
// 设置时间戳字段
protected $createTime = 'create_at';
protected $updateTime = 'update_at';
// 定义 JSONB 类型字段
protected $json = ['metadata'];
// 类型判断常量
const TYPE_YOUTUBE_VIDEO = 2;
const TYPE_IMAGE = 4;
// 获取类型
public function getTypeTextAttr($value, $data)
{
$statusMap = [
self::TYPE_YOUTUBE_VIDEO => 'YOUTUBE_VIDEO',
self::TYPE_IMAGE => 'IMAGE'
];
return $statusMap[$data['asset_type']] ?? 'UNKNOWN';
}
// 允许批量赋值的字段
protected $fillable = ['customer_id', 'asset_type', 'asset_name', 'resource_name','asset_url', 'status', 'metadata'];
// 关联到广告、广告组和广告活动
public function relations()
{
return $this->hasMany(AssetRelation::class, 'asset_id', 'asset_id');
}
// 获取素材的广告、广告组和广告活动
public function getRelations()
{
return $this->relations()->with(['ad', 'adGroup', 'campaign']);
}
// 追加自定义字段到模型结果中
public function appendCustomAttributes()
{
return ['relations'];
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace app\model;
use think\Model;
class AssetRelation extends Model
{
// 设置表名
protected $table = 'bps.bps_google_ads_asset_relations';
// 设置主键
protected $pk = 'asset_relation_id';
// 设置时间戳字段
protected $createTime = 'create_at';
protected $updateTime = 'update_at';
// 关联到广告
public function ad()
{
return $this->belongsTo(Ad::class, 'ad_id', 'ad_id');
}
// 关联到广告组
public function adGroup()
{
return $this->belongsTo(AdGroup::class, 'ad_group_id', 'ad_group_id');
}
// 关联到广告活动
public function campaign()
{
return $this->belongsTo(Campaign::class, 'campaign_id', 'campaign_id');
}
// 素材关联关系
public function asset()
{
return $this->belongsTo(Asset::class, 'asset_id', 'asset_id');
}
}

View File

@ -105,4 +105,9 @@ class Campaign extends Model
{
return $this->hasMany(DayData::class, 'campaign_id', 'campaign_id');
}
// 关联到素材关系
public function assetRelations()
{
return $this->hasMany(AssetRelation::class, 'campaign_id', 'campaign_id');
}
}

View File

@ -6,6 +6,7 @@ namespace app\process;
use app\event\GoogleAdsCampaigns;
use app\event\GoogleAdsGroups;
use app\event\GoogleAdsAds;
use app\event\GoogleAdsAssets;
use app\event\GoogleAdsDateDatas;
use Webman\Event\Event;
use Workerman\Crontab\Crontab;
@ -65,6 +66,13 @@ class UpdateGoogleAdsTask
dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAds::type . '开始');
Event::emit(GoogleAdsAds::type, []);
}
);
// 每15分钟执行一次
new Crontab('58 */15 * * * *', function () {
dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAssets::type . '开始');
Event::emit(GoogleAdsAssets::type, []);
}
);
// 每15分钟执行一次

5
app/schedule.php Normal file
View File

@ -0,0 +1,5 @@
<?php
use think\facade\Scheduler;
Scheduler::call('php think sync:google_ads_asset')
->everyDay(); // 每天运行一次

View File

@ -78,7 +78,7 @@ class GoogleAdsAdService extends BaseService
// Creates a single shared budget to be used by the campaigns added below.
$groupAdsResourceName = self::getAds($googleAdsClient->getGoogleAdsClient(), $customerId);
// dump(json_encode($groupadsResourceName));
// dump(json_encode($groupAdsResourceName));
if (is_array($groupAdsResourceName)) {
self::saveAds($groupAdsResourceName);
}
@ -88,7 +88,7 @@ class GoogleAdsAdService extends BaseService
/**
* 在数据库中保存广告系列信息
* 在数据库中保存广告信息
* @param $groupadsResourceName
* @return void
*/
@ -97,24 +97,41 @@ class GoogleAdsAdService extends BaseService
$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, campaign_id,customer_id, ad_name, status, resource_name)
VALUES (:ad_id, :ad_group_id,:campaign_id, :customer_id, :ad_name, :status, :resource_name)
ON CONFLICT (ad_id)
DO UPDATE SET
ad_group_id = EXCLUDED.ad_group_id,
campaign_id = EXCLUDED.campaign_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);
// 构建 SQL 语句,添加 ad_type 和 metadata
$sql = "INSERT INTO {$tableName}
(ad_id, ad_group_id, campaign_id, customer_id, ad_name, status, resource_name, ad_type, metadata)
VALUES
(:ad_id, :ad_group_id, :campaign_id, :customer_id, :ad_name, :status, :resource_name, :ad_type, :metadata)
ON CONFLICT (ad_id)
DO UPDATE SET
ad_group_id = EXCLUDED.ad_group_id,
campaign_id = EXCLUDED.campaign_id,
customer_id = EXCLUDED.customer_id,
ad_name = EXCLUDED.ad_name,
status = EXCLUDED.status,
resource_name = EXCLUDED.resource_name,
ad_type = EXCLUDED.ad_type,
metadata = EXCLUDED.metadata,
update_at = EXCLUDED.update_at";
// 绑定数据
$bindData = [
'ad_id' => $data['ad_id'],
'ad_group_id' => $data['ad_group_id'],
'campaign_id' => $data['campaign_id'],
'customer_id' => $data['customer_id'],
'ad_name' => $data['ad_name'],
'status' => $data['status'],
'resource_name' => $data['resource_name'],
'ad_type' => $data['ad_type'],
'metadata' => $data['metadata'],
];
// 执行 SQL 插入语句
ThinkDb::execute($sql, $bindData);
}
}
/**
* Runs the example.
*
@ -123,7 +140,8 @@ class GoogleAdsAdService extends BaseService
*/
// [START get_campaigns]
public static function getAds(GoogleAdsClient $googleAdsClient, int $customerId)
public
static function getAds(GoogleAdsClient $googleAdsClient, int $customerId)
{
$googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
// Creates a query that retrieves all groups.
@ -136,7 +154,13 @@ class GoogleAdsAdService extends BaseService
customer.id,
ad_group_ad.ad.name,
ad_group_ad.status,
ad_group_ad.ad.resource_name
ad_group_ad.ad.resource_name,
ad_group_ad.ad.type,
ad_group_ad.ad.responsive_display_ad.logo_images,
ad_group_ad.ad.responsive_display_ad.square_logo_images,
ad_group_ad.ad.responsive_display_ad.marketing_images,
ad_group_ad.ad.responsive_display_ad.square_marketing_images,
ad_group_ad.ad.responsive_display_ad.youtube_videos
FROM ad_group_ad
WHERE
ad_group_ad.status != 'REMOVED' ";
@ -154,6 +178,7 @@ class GoogleAdsAdService extends BaseService
// $finalUrlsList = $googleAdsRow->getAdGroupAd()->getAd()->getFinalUrls();
// 将最终的 URL 列表转换为 PHP 数组
// $finalUrlsArray = iterator_to_array($finalUrlsList);
$resourceName = [];
$resourceName['ad_id'] = $googleAdsRow->getAdGroupAd()->getAd()->getId();
$resourceName['ad_name'] = $googleAdsRow->getAdGroupAd()->getAd()->getName();
$resourceName['ad_group_id'] = $googleAdsRow->getAdGroup()->getId();
@ -162,13 +187,51 @@ class GoogleAdsAdService extends BaseService
// $resourceName['final_urls'] = $finalUrlsArray;
$resourceName['status'] = $googleAdsRow->getAdGroupAd()->getStatus();
$resourceName['resource_name'] = $googleAdsRow->getAdGroupAd()->getAd()->getResourceName();
$resourceNames[] = $resourceName;
$resourceName['ad_type'] = $googleAdsRow->getAdGroupAd()->getAd()->getType();
//ad_type 19=> RESPONSIVE_DISPLAY_AD 自适应展示广告 详细看model定义
if ($resourceName['ad_type'] === 19) {
// 获取 squareMarketingImages 中的每个 asset
$squareMarketingImages = $googleAdsRow->getAdGroupAd()->getAd()->getResponsiveDisplayAd()->getSquareMarketingImages();
$squareMarketingAssets = [];
foreach ($squareMarketingImages as $image) {
$squareMarketingAssets[] = $image->getAsset();
}
// 获取 logoImages 中的每个 asset
$logoImages = $googleAdsRow->getAdGroupAd()->getAd()->getResponsiveDisplayAd()->getLogoImages();
$logoAssets = [];
foreach ($logoImages as $logo) {
$logoAssets[] = $logo->getAsset();
}
// 获取 marketingImages 中的每个 asset
$marketingImages = $googleAdsRow->getAdGroupAd()->getAd()->getResponsiveDisplayAd()->getMarketingImages();
$marketingAssets = [];
foreach ($marketingImages as $marketing) {
$marketingAssets[] = $marketing->getAsset();
}
// 获取 youtubeVideos 中的每个 asset
$youtubeVideos = $googleAdsRow->getAdGroupAd()->getAd()->getResponsiveDisplayAd()->getYoutubeVideos();
$youtubeAssets = [];
foreach ($youtubeVideos as $video) {
$youtubeAssets[] = $video->getAsset();
}
// 将提取的资产保存到 metadata 数组
$resourceName['metadata']['square_marketing_images'] = $squareMarketingAssets;
$resourceName['metadata']['logo_images'] = $logoAssets;
$resourceName['metadata']['marketing_images'] = $marketingAssets;
$resourceName['metadata']['youtube_videos'] = $youtubeAssets;
// 将 metadata 转换为 JSON 格式
$resourceName['metadata'] = json_encode($resourceName['metadata']); // 存储为 JSONB 格式
} else {
$resourceName['metadata'] = null;
}
$resourceNames[] = $resourceName;
}
return $resourceNames;
}
/**
* This example updates the CPC bid and status for a given ad group. To get ad groups, run
* GetAdAds.php.
@ -178,7 +241,8 @@ class GoogleAdsAdService extends BaseService
* @return mixed
* @throws ApiException
*/
public function runUpdateAd($options): mixed
public
function runUpdateAd($options): mixed
{
// $googleAdsClient = $this->googleAdsClient;
$googleAdsClient = new GoogleAdsClientService($options['customer_id']);
@ -198,7 +262,8 @@ class GoogleAdsAdService extends BaseService
* @param int $adGroupId the ad group ID that the ad group ad belongs to
* @param int $adId the ID of the ad to pause
*/
public static function updateAd(
public
static function updateAd(
GoogleAdsClient $googleAdsClient,
int $customerId,
int $adGroupId,
@ -243,7 +308,8 @@ class GoogleAdsAdService extends BaseService
/**
* 更新广告状态
*/
public function updateAdStatus(int $customerId, int $adGroupId, int $adId, int $status)
public
function updateAdStatus(int $customerId, int $adGroupId, int $adId, int $status)
{
// 从数据库获取 Ad
$ad = AdModel::find($adId);
@ -267,7 +333,8 @@ class GoogleAdsAdService extends BaseService
/**
* 获取广告状态
*/
public function getAdStatus(int $adId)
public
function getAdStatus(int $adId)
{
// 从数据库获取 Ad
$ad = AdModel::find($adId);
@ -280,7 +347,6 @@ class GoogleAdsAdService extends BaseService
}
/**
* 判断广告是否启用
*/
@ -330,7 +396,8 @@ class GoogleAdsAdService extends BaseService
* @return mixed
* @throws ApiException
*/
public function runGetResponsiveSearchAds($options): mixed
public
function runGetResponsiveSearchAds($options): mixed
{
// $googleAdsClient = $this->googleAdsClient;
$googleAdsClient = new GoogleAdsClientService($options['customer_id']);
@ -351,7 +418,8 @@ class GoogleAdsAdService extends BaseService
* @param int|null $adGroupId the ad group ID for which responsive search ads will be retrieved.
* If `null`, returns from all ad groups
*/
public static function getResponsiveSearchAds(
public
static function getResponsiveSearchAds(
GoogleAdsClient $googleAdsClient,
int $customerId,
?int $adGroupId
@ -421,7 +489,8 @@ class GoogleAdsAdService extends BaseService
* @param RepeatedField $assets the list of AdTextAsset objects
* @return string the string representation of the provided list of AdTextAsset objects
*/
private static function convertAdTextAssetsToString(RepeatedField $assets): string
private
static function convertAdTextAssetsToString(RepeatedField $assets): string
{
$result = '';
foreach ($assets as $asset) {
@ -445,7 +514,8 @@ class GoogleAdsAdService extends BaseService
* @return mixed
* @throws ApiException
*/
public function runUpdateResponsiveSearchAd($options): mixed
public
function runUpdateResponsiveSearchAd($options): mixed
{
// $googleAdsClient = $this->googleAdsClient;
$googleAdsClient = new GoogleAdsClientService($options['customer_id']);
@ -463,7 +533,8 @@ class GoogleAdsAdService extends BaseService
* @param int $adId the ad ID to update
*/
// [START update_responsive_search_ad]
public static function updateResponsiveSearchAd(
public
static function updateResponsiveSearchAd(
GoogleAdsClient $googleAdsClient,
int $customerId,
int $adId
@ -514,6 +585,7 @@ class GoogleAdsAdService extends BaseService
);
return $updatedAd->getResourceName();
}
// [END update_responsive_search_ad]
@ -525,7 +597,8 @@ class GoogleAdsAdService extends BaseService
* @return mixed
* @throws ApiException
*/
public function runSearchForGoogleAdsFields($options): mixed
public
function runSearchForGoogleAdsFields($options): mixed
{
// $googleAdsClient = $this->googleAdsClient;
$googleAdsClient = new GoogleAdsClientService($options['customer_id']);
@ -541,7 +614,8 @@ class GoogleAdsAdService extends BaseService
* @param GoogleAdsClient $googleAdsClient the Google Ads API client
* @param string $namePrefix the name prefix to use in the query
*/
public static function searchForGoogleAdsFields(GoogleAdsClient $googleAdsClient, string $namePrefix)
public
static function searchForGoogleAdsFields(GoogleAdsClient $googleAdsClient, string $namePrefix)
{
$googleAdsFieldServiceClient = $googleAdsClient->getGoogleAdsFieldServiceClient();
// Searches for all fields whose name begins with the specified namePrefix.

View File

@ -0,0 +1,741 @@
<?php
namespace app\service;
use app\service\GoogleAdsClientService;
use app\model\ThirdUserAdvertiser;
use app\util\Helper;
use app\util\ArgumentNames;
use app\util\ArgumentParser;
use Google\Ads\GoogleAds\Lib\V18\GoogleAdsClient;
use Google\Ads\GoogleAds\Util\FieldMasks;
use Google\Ads\GoogleAds\Util\V18\ResourceNames;
use Google\Ads\GoogleAds\V18\Common\ResponsiveSearchAdInfo;
use Google\Ads\GoogleAds\V18\Enums\AdGroupAdStatusEnum\AdGroupAdStatus;
use Google\Ads\GoogleAds\V18\Enums\GoogleAdsFieldCategoryEnum\GoogleAdsFieldCategory;
use Google\Ads\GoogleAds\V18\Enums\GoogleAdsFieldDataTypeEnum\GoogleAdsFieldDataType;
use Google\Ads\GoogleAds\V18\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V18\Resources\Ad;
use Google\Ads\GoogleAds\V18\Resources\AdGroupAd;
use Google\Ads\GoogleAds\V18\Resources\GoogleAdsField;
use Google\Ads\GoogleAds\V18\Services\AdGroupAdOperation;
use Google\Ads\GoogleAds\V18\Services\AdOperation;
use Google\Ads\GoogleAds\V18\Services\MutateAdGroupAdsRequest;
use Google\Ads\GoogleAds\V18\Common\AdTextAsset;
use Google\Ads\GoogleAds\V18\Enums\ServedAssetFieldTypeEnum\ServedAssetFieldType;
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 app\model\Ad as AdModel;
use Google\ApiCore\ApiException;
class GoogleAdsAssetService extends BaseService
{
// private $googleAdsClient;
private $customerId;
public function __construct($customerId = null)
{
}
// 从数据库动态获取 google RefreshToken
// private function getRefreshTokenFromDatabase($advertiserId)
// {
// // 通过 advertiser_id 查询 ThirdUserAdvertiser联表查询 ThirdUser 数据
// $userAdvertiser = ThirdUserAdvertiser::with('googleUser') // 联表查询 user 关联
// ->where('advertiser_id', $advertiserId) // 根据 advertiser_id 查询
// ->find(); // 获取第一个结果
//
//// 如果找到广告主数据
// if ($userAdvertiser && $userAdvertiser->googleUser) {
// // 获取关联用户的 access_token
// return $userAdvertiser->googleUser ? $userAdvertiser->googleUser->access_token : null;
// } else {
//// return $this->errorResponse('101', '未找到该广告主或关联的用户');
// }
// }
/* @param int $customerId the customer ID
* @param $options
* @return mixed
* @throws ApiException
*/
public function runListAssets(int $customerId): mixed
{
$googleAdsClient = new GoogleAdsClientService($customerId);
// Creates a single shared budget to be used by the campaigns added below.
$assetsResourceName = self::getAssets($googleAdsClient->getGoogleAdsClient(), $customerId);
// dump(json_encode($assetsResourceName));
if (is_array($assetsResourceName)) {
self::saveAssets($assetsResourceName);
}
return $assetsResourceName;
}
/* @param int $customerId the customer ID
* @param $options
* @return mixed
* @throws ApiException
*/
public function runListAds(int $customerId): mixed
{
// $googleAdsClient = $this->googleAdsClient;
$googleAdsClient = new GoogleAdsClientService($customerId);
// Creates a single shared budget to be used by the campaigns added below.
$groupAdsResourceName = self::getAds($googleAdsClient->getGoogleAdsClient(), $customerId);
// dump(json_encode($groupadsResourceName));
if (is_array($groupAdsResourceName)) {
self::saveAds($groupAdsResourceName);
}
return $groupAdsResourceName;
}
/**
* 在数据库中保存广告素材信息
* @param $assetsResourceName
* @return void
*/
public static function saveAssets($assetsResourceName)
{
$tableName = 'bps_google_ads_asset';
$tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'public' . $tableName;
foreach ($assetsResourceName as $data) {
// 修改后的插入 SQL 语句
$sql = "INSERT INTO {$tableName}
(asset_id, customer_id, asset_type, asset_name, resource_name, asset_url, status, metadata, create_at, update_at)
VALUES (:asset_id, :customer_id, :asset_type, :asset_name, :resource_name, :asset_url, :status, :metadata, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT (asset_id)
DO UPDATE SET
customer_id = EXCLUDED.customer_id,
asset_type = EXCLUDED.asset_type,
asset_name = EXCLUDED.asset_name,
resource_name = EXCLUDED.resource_name,
asset_url = EXCLUDED.asset_url,
status = EXCLUDED.status,
metadata = EXCLUDED.metadata,
update_at = CURRENT_TIMESTAMP"; // update_at 使用 CURRENT_TIMESTAMP 自动更新
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 getAssets(GoogleAdsClient $googleAdsClient, int $customerId)
{
$googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
// 查询广告素材及其关联的广告、广告组和广告系列
// $query ="SELECT asset.id,asset.name, asset.image_asset.mime_type, asset.image_asset.full_size.url, customer.id FROM asset";
$query = "SELECT asset.id,
asset.name,
asset.type,
asset.resource_name,
asset.source,
asset.image_asset.full_size.url,
asset.image_asset.mime_type,
asset.image_asset.file_size,
asset.image_asset.full_size.height_pixels,
asset.image_asset.full_size.width_pixels,
asset.youtube_video_asset.youtube_video_id,
asset.youtube_video_asset.youtube_video_title,
customer.id
FROM asset
WHERE
asset.type IN ('IMAGE', 'YOUTUBE_VIDEO')
LIMIT 10000";
// 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 */
// 将最终的 URL 列表转换为 PHP 数组
// $finalUrlsArray = iterator_to_array($finalUrlsList);
$resourceName = [];
$resourceName['asset_type'] = $googleAdsRow->getAsset()->getType();
if ($resourceName['asset_type'] === 2) {
$resourceName['asset_url'] = $googleAdsRow->getAsset()->getYoutubeVideoAsset()->getYoutubeVideoId();
$resourceName['metadata'] ['youtube_video_id'] = $googleAdsRow->getAsset()->getYoutubeVideoAsset()->getYoutubeVideoId();
$resourceName['metadata'] ['youtube_video_title'] = $googleAdsRow->getAsset()->getYoutubeVideoAsset()->getYoutubeVideoTitle();
$resourceName['asset_name'] = $resourceName['metadata'] ['youtube_video_title'];
$resourceName['metadata'] = json_encode($resourceName['metadata']);
} elseif ($resourceName['asset_type'] === 4) {
$resourceName['asset_name'] = $googleAdsRow->getAsset()->getName();
$resourceName['asset_url'] = $googleAdsRow->getAsset()->getImageAsset()->getFullSize()->getUrl();
$resourceName['metadata'] ['mimeType'] = $googleAdsRow->getAsset()->getImageAsset()->getMimeType();
$resourceName['metadata'] ['filesize'] = $googleAdsRow->getAsset()->getImageAsset()->getFileSize();
$resourceName['metadata'] ['height_pixels'] = $googleAdsRow->getAsset()->getImageAsset()->getFullSize()->getHeightPixels();
$resourceName['metadata'] ['width_pixels'] = $googleAdsRow->getAsset()->getImageAsset()->getFullSize()->getWidthPixels();
$resourceName['metadata'] = json_encode($resourceName['metadata']);
} else {
continue;
}
$resourceName['asset_id'] = $googleAdsRow->getAsset()->getId();
$resourceName['status'] = 2; //未定义,先占坑
$resourceName['resource_name'] = $googleAdsRow->getAsset()->getResourceName();
$resourceName['customer_id'] = $googleAdsRow->getCustomer()->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]
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,
campaign.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['campaign_id'] = $googleAdsRow->getCampaign()->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
* GetAdAds.php.
*/
/* @param int $customerId the customer ID
* @param $options
* @return mixed
* @throws ApiException
*/
public function runUpdateAd($options): mixed
{
// $googleAdsClient = $this->googleAdsClient;
$googleAdsClient = new GoogleAdsClientService($options['customer_id']);
// Creates a single shared budget to be used by the campaigns added below.
$resourceNames = self::updateAd($googleAdsClient->getGoogleAdsClient(), $options['customer_id'], $options['group_id'], $options['ad_id'], $options['status']);
return $resourceNames;
}
/**
* Runs the updateAd example.
*
* @param GoogleAdsClient $googleAdsClient the Google Ads API client
* @param int $customerId the customer ID
* @param int $adGroupId the ad group ID that the ad group ad belongs to
* @param int $adId the ID of the ad to pause
*/
public static function updateAd(
GoogleAdsClient $googleAdsClient,
int $customerId,
int $adGroupId,
int $adId,
int $status
)
{
// Creates ad group ad resource name.
$adGroupAdResourceName = ResourceNames::forAdGroupAd($customerId, $adGroupId, $adId);
// Creates an ad and sets its status to PAUSED.
$adGroupAd = new AdGroupAd();
$adGroupAd->setResourceName($adGroupAdResourceName);
// $adGroupAd->setStatus(AdGroupAdStatus::PAUSED);
$adGroupAd->setStatus($status);
// Constructs an operation that will pause the ad with the specified resource name,
// using the FieldMasks utility to derive the update mask. This mask tells the Google Ads
// API which attributes of the ad group you want to change.
$adGroupAdOperation = new AdGroupAdOperation();
$adGroupAdOperation->setUpdate($adGroupAd);
$adGroupAdOperation->setUpdateMask(FieldMasks::allSetFieldsOf($adGroupAd));
// Issues a mutate request to pause the ad group ad.
$adGroupAdServiceClient = $googleAdsClient->getAdGroupAdServiceClient();
$response = $adGroupAdServiceClient->mutateAdGroupAds(MutateAdGroupAdsRequest::build(
$customerId,
[$adGroupAdOperation]
));
// Prints the resource name of the paused ad group ad.
/** @var AdGroupAd $pausedAdGroupAd */
$pausedAdGroupAd = $response->getResults()[0];
// printf(
// "Ad group ad with resource name: '%s' is paused.%s",
// $pausedAdGroupAd->getResourceName(),
// PHP_EOL
// );
return $pausedAdGroupAd->getResourceName();
}
/**
* 更新广告状态
*/
public function updateAdStatus(int $customerId, int $adGroupId, int $adId, int $status)
{
// 从数据库获取 Ad
$ad = AdModel::find($adId);
if (!$ad) {
// throw new ValidateException('Ad not found');
return false;
}
// 更新数据库中的状态
// $ad->updateStatus($status);
if ($this->modifyDbAdStatus($adId, $status)) {
// 更新 Google Ads 上的状态
// $googleAdsClient = $this->googleAdsClient;
$googleAdsClient = new GoogleAdsClientService($customerId);
$resourceName = self::updateAd($googleAdsClient->getGoogleAdsClient(), $customerId, $adGroupId, $adId, $status);
return true;
}
return false;
}
/**
* 获取广告状态
*/
public function getAdStatus(int $adId)
{
// 从数据库获取 Ad
$ad = AdModel::find($adId);
if (!$ad) {
// throw new ValidateException('Ad not found');
}
// 返回广告状态
return $ad->getStatusTextAttr(null, $ad->toArray());
}
/**
* 判断广告是否启用
*/
// public function isAdEnabled(int $adId)
// {
// $ad = Ad::find($adId);
// if (!$ad) {
// throw new ValidateException('Ad not found');
// }
//
// return $ad->isEnabled();
// }
/**
* 判断广告是否暂停
*/
// public function isAdPaused(int $adId)
// {
// $ad = Ad::find($adId);
// if (!$ad) {
// throw new ValidateException('Ad not found');
// }
//
// return $ad->isPaused();
// }
/**
* 判断广告是否停止
*/
// public function isAdStopped(int $adId)
// {
// $ad = Ad::find($adId);
// if (!$ad) {
// throw new ValidateException('Ad not found');
// }
//
// return $ad->isStopped();
// }
/**
* This example updates the CPC bid and status for a given ad group. To get ad groups, run
* GetAdAds.php.
*/
/* @param int $customerId the customer ID
* @param $options
* @return mixed
* @throws ApiException
*/
public function runGetResponsiveSearchAds($options): mixed
{
// $googleAdsClient = $this->googleAdsClient;
$googleAdsClient = new GoogleAdsClientService($options['customer_id']);
// Creates a single shared budget to be used by the campaigns added below.
if (!isset($options['group_id'])) {
$options['group_id'] = null;
}
$resourceNames = self::getResponsiveSearchAds($googleAdsClient->getGoogleAdsClient(), $options['customer_id'], $options['group_id']);
return $resourceNames;
}
/**
* 获取指定广告组中未移除的自适应搜索广告。
*
* @param GoogleAdsClient $googleAdsClient the Google Ads API client
* @param int $customerId the customer ID
* @param int|null $adGroupId the ad group ID for which responsive search ads will be retrieved.
* If `null`, returns from all ad groups
*/
public static function getResponsiveSearchAds(
GoogleAdsClient $googleAdsClient,
int $customerId,
?int $adGroupId
)
{
$googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
// Creates a query that retrieves responsive search ads.
$query =
'SELECT ad_group.id, '
. 'ad_group_ad.ad.id, '
. 'ad_group_ad.ad.responsive_search_ad.headlines, '
. 'ad_group_ad.ad.responsive_search_ad.descriptions, '
. 'ad_group_ad.status '
. 'FROM ad_group_ad '
. 'WHERE ad_group_ad.ad.type = RESPONSIVE_SEARCH_AD '
. 'AND ad_group_ad.status != "REMOVED"';
if (!is_null($adGroupId)) {
$query .= " AND ad_group.id = $adGroupId";
}
// Issues a search request.
$response =
$googleAdsServiceClient->search(SearchGoogleAdsRequest::build($customerId, $query));
// Iterates over all rows in all pages and prints the requested field values for
// the responsive search ad in each row.
$isEmptyResult = true;
$resources = [];
foreach ($response->iterateAllElements() as $googleAdsRow) {
/** @var GoogleAdsRow $googleAdsRow */
$isEmptyResult = false;
$ad = $googleAdsRow->getAdGroupAd()->getAd();
$resource = [];
// printf(
// "Responsive search ad with resource name '%s' and status '%s' was found.%s",
// $ad->getResourceName(),
// AdGroupAdStatus::name($googleAdsRow->getAdGroupAd()->getStatus()),
// PHP_EOL
// );
$resource['resource_name'] = $ad->getResourceName();
$resource['status'] = AdGroupAdStatus::name($googleAdsRow->getAdGroupAd()->getStatus());
$responsiveSearchAdInfo = $ad->getResponsiveSearchAd();
// printf(
// 'Headlines:%1$s%2$sDescriptions:%1$s%3$s%1$s',
// PHP_EOL,
// self::convertAdTextAssetsToString($responsiveSearchAdInfo->getHeadlines()),
// self::convertAdTextAssetsToString($responsiveSearchAdInfo->getDescriptions())
// )
$resource['content'] = sprintf(
'Headlines:%1$s%2$sDescriptions:%1$s%3$s%1$s',
PHP_EOL,
self::convertAdTextAssetsToString($responsiveSearchAdInfo->getHeadlines()),
self::convertAdTextAssetsToString($responsiveSearchAdInfo->getDescriptions())
);
$resources[] = $resource;
}
return $resources;
// if ($isEmptyResult) {
// print 'No responsive search ads were found.' . PHP_EOL;
// }
}
/**
* Converts the list of AdTextAsset objects into a string representation.
*
* @param RepeatedField $assets the list of AdTextAsset objects
* @return string the string representation of the provided list of AdTextAsset objects
*/
private static function convertAdTextAssetsToString(RepeatedField $assets): string
{
$result = '';
foreach ($assets as $asset) {
/** @var AdTextAsset $asset */
$result .= sprintf(
"\t%s pinned to %s.%s",
$asset->getText(),
ServedAssetFieldType::name($asset->getPinnedField()),
PHP_EOL
);
}
return $result;
}
/**
* This example updates the CPC bid and status for a given ad group. To get ad groups, run
* GetAdAds.php.
*/
/* @param int $customerId the customer ID
* @param $options
* @return mixed
* @throws ApiException
*/
public function runUpdateResponsiveSearchAd($options): mixed
{
// $googleAdsClient = $this->googleAdsClient;
$googleAdsClient = new GoogleAdsClientService($options['customer_id']);
// Creates a single shared budget to be used by the campaigns added below.
$resourceName = self::updateResponsiveSearchAd($googleAdsClient->getGoogleAdsClient(), $options['customer_id'], $options['ad_id']);
return $resourceName;
}
/**
* updateResponsiveSearchAd.
*
* @param GoogleAdsClient $googleAdsClient the Google Ads API client
* @param int $customerId the customer ID
* @param int $adId the ad ID to update
*/
// [START update_responsive_search_ad]
public static function updateResponsiveSearchAd(
GoogleAdsClient $googleAdsClient,
int $customerId,
int $adId
)
{
// Creates an ad with the specified resource name and other changes.
$ad = new Ad([
'resource_name' => ResourceNames::forAd($customerId, $adId),
'responsive_search_ad' => new ResponsiveSearchAdInfo([
// Update some properties of the responsive search ad.
'headlines' => [
new AdTextAsset([
'text' => 'Cruise to Pluto #' . Helper::getShortPrintableDatetime(),
'pinned_field' => ServedAssetFieldType::HEADLINE_1
]),
new AdTextAsset(['text' => 'Tickets on sale now', 'pinned_field' => ServedAssetFieldType::HEADLINE_2]),
new AdTextAsset(['text' => 'Buy your ticket now'])
],
'descriptions' => [
new AdTextAsset(['text' => 'Best space cruise ever.']),
new AdTextAsset([
'text' => 'The most wonderful space experience you will ever have.'])
]
]),
'final_urls' => ['http://www.baidu.com'],
'final_mobile_urls' => ['http://www.baidu.com/mobile']
]);
// Constructs an operation that will update the ad, using the FieldMasks to derive the
// update mask. This mask tells the Google Ads API which attributes of the ad you want to
// change.
$adOperation = new AdOperation();
$adOperation->setUpdate($ad);
$adOperation->setUpdateMask(FieldMasks::allSetFieldsOf($ad));
// Issues a mutate request to update the ad.
$adServiceClient = $googleAdsClient->getAdServiceClient();
$response =
$adServiceClient->mutateAds(MutateAdsRequest::build($customerId, [$adOperation]));
// Prints the resource name of the updated ad.
/** @var Ad $updatedAd */
$updatedAd = $response->getResults()[0];
printf(
"Updated ad with resource name: '%s'.%s",
$updatedAd->getResourceName(),
PHP_EOL
);
return $updatedAd->getResourceName();
}
// [END update_responsive_search_ad]
/**
* 根据给定的前缀搜索 Google Ads 字段检索到关于这些字段的元数据metadata
*/
/* @param int $customerId the customer ID
* @param $options
* @return mixed
* @throws ApiException
*/
public function runSearchForGoogleAdsFields($options): mixed
{
// $googleAdsClient = $this->googleAdsClient;
$googleAdsClient = new GoogleAdsClientService($options['customer_id']);
// Creates a single shared budget to be used by the campaigns added below.
$googleAdsFieldData = self::searchForGoogleAdsFields($googleAdsClient->getGoogleAdsClient(), $options['name_prefix']);
return $googleAdsFieldData;
}
/**
* Runs the example SearchForGoogleAdsFields.
*
* @param GoogleAdsClient $googleAdsClient the Google Ads API client
* @param string $namePrefix the name prefix to use in the query
*/
public static function searchForGoogleAdsFields(GoogleAdsClient $googleAdsClient, string $namePrefix)
{
$googleAdsFieldServiceClient = $googleAdsClient->getGoogleAdsFieldServiceClient();
// Searches for all fields whose name begins with the specified namePrefix.
// A single "%" is the wildcard token in the Google Ads Query language.
$query = "SELECT name, category, selectable, filterable, sortable, selectable_with, "
. "data_type, is_repeated WHERE name LIKE '$namePrefix%'";
$response = $googleAdsFieldServiceClient->searchGoogleAdsFields(
SearchGoogleAdsFieldsRequest::build($query)
);
if (iterator_count($response->getIterator()) === 0) {
printf(
"No GoogleAdsFields found with a name that begins with %s.%s",
$namePrefix,
PHP_EOL
);
return;
}
// Iterates over all rows and prints our the metadata of each matching GoogleAdsField.
foreach ($response->iterateAllElements() as $googleAdsField) {
/** @var GoogleAdsField $googleAdsField */
$fieldInfo = [
'name' => $googleAdsField->getName(),
'category' => GoogleAdsFieldCategory::name($googleAdsField->getCategory()),
'data_type' => GoogleAdsFieldDataType::name($googleAdsField->getDataType()),
'selectable' => $googleAdsField->getSelectable() ? 'true' : 'false',
'filterable' => $googleAdsField->getFilterable() ? 'true' : 'false',
'sortable' => $googleAdsField->getSortable() ? 'true' : 'false',
'repeated' => $googleAdsField->getIsRepeated() ? 'true' : 'false',
'selectable_with' => []
];
// Check if there are fields that are selectable with the current field
if ($googleAdsField->getSelectableWith()->count() > 0) {
$selectableWithFields = iterator_to_array($googleAdsField->getSelectableWith()->getIterator());
sort($selectableWithFields); // Sort the fields alphabetically
$fieldInfo['selectable_with'] = $selectableWithFields;
}
// Add the field info to the result array
$googleAdsFieldData[] = $fieldInfo;
//
// printf("%s:%s", $googleAdsField->getName(), PHP_EOL);
// printf(
// " %-16s: %s%s",
// "category:",
// GoogleAdsFieldCategory::name($googleAdsField->getCategory()),
// PHP_EOL
// );
// printf(
// " %-16s: %s%s",
// "data type:",
// GoogleAdsFieldDataType::name($googleAdsField->getDataType()),
// PHP_EOL
// );
// printf(
// " %-16s: %s%s",
// "selectable:",
// $googleAdsField->getSelectable() ? 'true' : 'false',
// PHP_EOL
// );
// printf(
// " %-16s: %s%s",
// "filterable:",
// $googleAdsField->getFilterable() ? 'true' : 'false',
// PHP_EOL
// );
// printf(
// " %-16s: %s%s",
// "sortable:",
// $googleAdsField->getSortable() ? 'true' : 'false',
// PHP_EOL
// );
// printf(
// " %-16s: %s%s",
// "repeated:",
// $googleAdsField->getIsRepeated() ? 'true' : 'false',
// PHP_EOL
// );
//
// if ($googleAdsField->getSelectableWith()->count() > 0) {
// // Prints the list of fields that are selectable with the field.
// $selectableWithFields =
// iterator_to_array($googleAdsField->getSelectableWith()->getIterator());
// // Sorts and then prints the list.
// sort($selectableWithFields);
// print ' selectable with:' . PHP_EOL;
// foreach ($selectableWithFields as $selectableWithField) {
// /** @var string $selectableWithField */
// printf(" $selectableWithField%s", PHP_EOL);
// }
// }
}
return $googleAdsFieldData; // Return the result array
}
}

View File

@ -6,6 +6,7 @@ use app\event\TiktokAdsDetails;
use app\event\GoogleAdsCampaigns;
use app\event\GoogleAdsGroups;
use app\event\GoogleAdsAds;
use app\event\GoogleAdsAssets;
use app\event\GoogleAdsDateDatas;
@ -29,6 +30,9 @@ return [
GoogleAdsAds::type => [
[GoogleAdsAds::class, 'getAds'],
],
GoogleAdsAssets::type => [
[GoogleAdsAssets::class, 'getAssets'],
],
];

View File

@ -100,6 +100,9 @@ Route::group('/googleads', function () {
Route::group('/group', function () {
Route::post('/list', [GoogleAdsController::class, 'listGroups']);
});
Route::group('/asset', function () {
Route::post('/list', [GoogleAdsController::class, 'listAssets']);
});
Route::group('/ad', function () {
Route::post('/update', [GoogleAdsController::class, 'updateAd']);
Route::post('/list', [GoogleAdsController::class, 'listAds']);