谷歌创意素材2

This commit is contained in:
hgc 2024-12-27 17:07:27 +08:00
parent f145391b52
commit f385f6f2ef
8 changed files with 1062 additions and 18 deletions

View File

@ -10,6 +10,7 @@ use app\service\GoogleAdsCampaignService;
use app\service\GoogleAdsGroupService;
use app\service\GoogleAdsAdService;
use app\service\GoogleAdsAssetService;
use app\service\GoogleAdsAssetRelationService;
use app\service\GoogleAdsAccountService;
use support\Response;
use DI\Annotation\Inject;
@ -42,6 +43,12 @@ class GoogleAdsController
*/
private $googleAdsAssetService;
/**
* @Inject
* @var GoogleAdsAssetRelationService
*/
private $googleAdsAssetRelationService;
/**
* @Inject
* @var GoogleAdsAccountService
@ -93,6 +100,17 @@ class GoogleAdsController
// 继续处理 Google Ads API 操作
return $this->getAssets($options);
}
public function listAssetRelations(Request $request)
{
$options = $request->all();
// 继续处理 Google Ads API 操作
if($options['asset_type'] == 2){
return $this->getVideoAssetRelations($options);
}elseif($options['asset_type'] == 4){
$this->getAssetRelations($options);
}
}
public function listDateDatas(Request $request)
{
@ -279,6 +297,27 @@ class GoogleAdsController
// return $this->successResponse(['assets_list' => $resourceName]);
return $this->successResponse(['assets_list' => 'succeed added']);
}
/**
* get assets
* @throws ApiException
*/
public function getAssetRelations($options): Response
{
$resourceName = $this->googleAdsAssetRelationService->runListAssetRelations($options['customer_id']);
return $this->successResponse(['assets_relation_list' => $resourceName]);
// return $this->successResponse(['assets_relation_list' => 'succeed added']);
}
/**
* get assets
* @throws ApiException
*/
public function getVideoAssetRelations($options): Response
{
$resourceName = $this->googleAdsAssetRelationService->runListVideoAssetRelations($options['customer_id']);
return $this->successResponse(['assets_relation_list' => $resourceName]);
// return $this->successResponse(['assets_relation_list' => 'succeed added']);
}
/**
* get date datas

View File

@ -0,0 +1,297 @@
<?php
namespace app\event;
use app\service\GoogleAdsAssetRelationService;
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 GoogleAdsAssetRelations
{
/**
* @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 IMAGEASSET = 'googleadsassetrelations_image';
const VIDEOASSET = 'googleadsassetrelations_video';
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 listAssetRelations(Request $request)
{
$options = $request->all();
// 继续处理 Google Ads API 操作
return $this->getAssetRelations($options);
}
/**
* get asset relations
* @throws ApiException
*/
public function getAssetRelations($options)
{
$customers = $this->googleOAuthService->getGoogleAdCustomers([]);
foreach ($customers as $customerId) {
$googleAdsAssetRelationService = new GoogleAdsAssetRelationService($customerId);
$resourceName = $googleAdsAssetRelationService->runListAssetRelations($customerId);
}
// return $this->successResponse(['ads_list' => $resourceName]);
}
/**
* get asset relations
* @throws ApiException
*/
public function getVideoAssetRelations($options)
{
$customers = $this->googleOAuthService->getGoogleAdCustomers([]);
foreach ($customers as $customerId) {
$googleAdsAssetRelationService = new GoogleAdsAssetRelationService($customerId);
$resourceName = $googleAdsAssetRelationService->runListVideoAssetRelations($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

@ -25,6 +25,11 @@ class Ad extends Model
'ad_group_id' => 'int',
'customer_id' => 'int',
];
// 设置json类型字段
protected $json = ['metadata'];
// 默认值设置
protected $defaults = [
'status' => 1, // 广告状态默认值为 'ENABLED'

View File

@ -7,6 +7,7 @@ use app\event\GoogleAdsCampaigns;
use app\event\GoogleAdsGroups;
use app\event\GoogleAdsAds;
use app\event\GoogleAdsAssets;
use app\event\GoogleAdsAssetRelations;
use app\event\GoogleAdsDateDatas;
use Webman\Event\Event;
use Workerman\Crontab\Crontab;
@ -24,33 +25,33 @@ class UpdateGoogleAdsTask
// 每15分钟执行一次
new Crontab('10 */15 * * * *', function () {
$dayBeforeYesterdayStart = date('Y-m-d', strtotime('-2 day'));
dump($dayBeforeYesterdayStart . '更新' . GoogleAdsDateDatas::type . '开始');
Event::emit(GoogleAdsDateDatas::type, ['date' => $dayBeforeYesterdayStart]);
// $dayBeforeYesterdayStart = date('Y-m-d', strtotime('-2 day'));
// dump($dayBeforeYesterdayStart . '更新' . GoogleAdsDateDatas::type . '开始');
// Event::emit(GoogleAdsDateDatas::type, ['date' => $dayBeforeYesterdayStart]);
}
);
// 每15分钟执行一次
new Crontab('20 */15 * * * *', function () {
$yesterdayStart = date('Y-m-d', strtotime('-1 day'));
dump($yesterdayStart . '更新' . GoogleAdsDateDatas::type . '开始');
Event::emit(GoogleAdsDateDatas::type, ['date' => $yesterdayStart]);
// $yesterdayStart = date('Y-m-d', strtotime('-1 day'));
// dump($yesterdayStart . '更新' . GoogleAdsDateDatas::type . '开始');
// Event::emit(GoogleAdsDateDatas::type, ['date' => $yesterdayStart]);
}
);
// 每15分钟执行一次
new Crontab('30 */15 * * * *', function () {
//获取今天的 0 点的YYYY-MM-DD格式
$todayStart = date('Y-m-d', strtotime('0 day'));
dump($todayStart . '更新' . GoogleAdsDateDatas::type . '开始');
Event::emit(GoogleAdsDateDatas::type, ['date' => $todayStart]);
// $todayStart = date('Y-m-d', strtotime('0 day'));
// dump($todayStart . '更新' . GoogleAdsDateDatas::type . '开始');
// Event::emit(GoogleAdsDateDatas::type, ['date' => $todayStart]);
}
);
// 每15分钟执行一次
new Crontab('40 */15 * * * *', function () {
dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsCampaigns::type . '开始');
Event::emit(GoogleAdsCampaigns::type, []);
// dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsCampaigns::type . '开始');
// Event::emit(GoogleAdsCampaigns::type, []);
}
);
@ -63,15 +64,26 @@ class UpdateGoogleAdsTask
// 每15分钟执行一次
new Crontab('55 */15 * * * *', function () {
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(GoogleAdsAds::type, []);
}
);
// 每15分钟执行一次
new Crontab('58 */15 * * * *', function () {
dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAssets::type . '开始');
Event::emit(GoogleAdsAssets::type, []);
new Crontab('25 */15 * * * *', function () {
// dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAssets::type . '开始');
// Event::emit(GoogleAdsAssets::type, []);
}
);
new Crontab('55 */50 * * * *', function () {
dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAssetRelations::IMAGEASSET . '开始');
Event::emit(GoogleAdsAssetRelations::IMAGEASSET, []);
});
new Crontab('55 */51 * * * *', function () {
dump(date('Y-m-d H:i:s') . '更新' . GoogleAdsAssetRelations::VIDEOASSET . '开始');
Event::emit(GoogleAdsAssetRelations::VIDEOASSET, []);
}
);

View File

@ -0,0 +1,679 @@
<?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 app\model\Asset as AssetModel;
use app\model\AssetRelation as AssetRelationModel;
use Google\ApiCore\ApiException;
class GoogleAdsAssetRelationService 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 runListAssetRelations(int $customerId): mixed
{
// Creates a single shared budget to be used by the campaigns added below.
$assetsResourceName = self::getAssetRelations($customerId);
// dump(json_encode($assetsResourceName));
if (is_array($assetsResourceName)) {
self::saveAssetRelations($assetsResourceName);
}
// return $assetsResourceName;
return 'insert success';
}
/* @param int $customerId the customer ID
* @param $options
* @return mixed
* @throws ApiException
*/
public function runListVideoAssetRelations(int $customerId): mixed
{
// Creates a single shared budget to be used by the campaigns added below.
$assetsResourceName = self::getVideoAssetRelations($customerId);
// dump(json_encode($assetsResourceName));
if (is_array($assetsResourceName)) {
self::saveAssetRelations($assetsResourceName);
}
// return $assetsResourceName;
return 'insert success';
}
/**
* 在数据库中保存广告素材信息
* @param $assetsResourceName
* @return void
*/
public static function saveAssetRelations($assetsResourceName)
{
$tableName = 'bps_google_ads_asset_relations';
$tableName = getenv('DB_PG_SCHEMA') ? getenv('DB_PG_SCHEMA') . '.' . $tableName : 'public' . $tableName;
foreach ($assetsResourceName as $data) {
// 修改后的插入 SQL 语句
$sql = "INSERT INTO {$tableName}
(asset_id, ad_id, ad_group_id, campaign_id, date, create_at, update_at)
VALUES (:asset_id, :ad_id, :ad_group_id, :campaign_id, :date, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT (asset_id, ad_id, date)
DO NOTHING"; // 如果 (asset_id, ad_id, date) 存在,忽略插入操作
ThinkDb::execute($sql, $data);
}
}
/**
*
* @param int $customerId the customer ID
*/
public static function getAssetRelations(int $customerId)
{
// 获取所有素材
// $assets = AssetModel::where('asset_type', 4)->select(); //图片素材
$resourceNames = AssetModel::where('asset_type', 4)
->column('asset_id, resource_name');
// dump($resourceNames);return($resourceNames);
$result = [];
foreach ($resourceNames as $resourceName) {
// 获取广告表中的所有广告
$ads = ThinkDb::table('bps.bps_google_ads_ad')
// ->whereRaw("metadata->'marketing_images' @> ?", ['["customers/4060397299/assets/191677352383"]'])
// ->whereOrRaw("metadata->'square_marketing_images' @> ?", ['["customers/4060397299/assets/191677352383"]'])
->whereRaw("metadata->'marketing_images' @> ?", ['["' . $resourceName['resource_name'] . '"]'])
->whereOrRaw("metadata->'square_marketing_images' @> ?", ['["' . $resourceName['resource_name'] . '"]'])
->select();
// $ads = AdModel::where(function ($query) use ($resourceName) {
// $query->whereJsonContains('metadata->marketing_images', $resourceName)
// ->WhereOrJsonContains('metadata->square_marketing_images', $resourceName);
// })->select();
// $result[$resourceName['asset_id']] = $ads;
foreach ($ads as $ad) {
$result[$resourceName['asset_id']]['ad_id'] = $ad['ad_id'];
$result[$resourceName['asset_id']]['ad_group_id'] = $ad['ad_group_id'];
$result[$resourceName['asset_id']]['campaign_id'] = $ad['campaign_id'];
$result[$resourceName['asset_id']]['asset_id'] = $resourceName['asset_id'];
$result[$resourceName['asset_id']]['date'] = date('Y-m-d');
}
}
return $result;
// dump('Google Ads Asset synchronization completed.');
}
/**
*
* @param int $customerId the customer ID
*/
public static function getVideoAssetRelations(int $customerId)
{
// 获取所有素材
// $assets = AssetModel::where('asset_type', 2)->select(); //视频素材
$resourceNames = AssetModel::where('asset_type', 2)
->column('asset_id, resource_name');
// dump($resourceNames);return($resourceNames);
$result = [];
foreach ($resourceNames as $resourceName) {
// 获取广告表中的所有匹配的广告
$ads = ThinkDb::table('bps.bps_google_ads_ad')
->whereRaw("metadata->'youtube_videos' @> ?", ['["' . $resourceName['resource_name'] . '"]'])
->select();
foreach ($ads as $ad) {
$result[$resourceName['asset_id']]['ad_id'] = $ad['ad_id'];
$result[$resourceName['asset_id']]['ad_group_id'] = $ad['ad_group_id'];
$result[$resourceName['asset_id']]['campaign_id'] = $ad['campaign_id'];
$result[$resourceName['asset_id']]['asset_id'] = $resourceName['asset_id'];
$result[$resourceName['asset_id']]['date'] = date('Y-m-d');
}
}
return $result;
// dump('Google Ads Asset synchronization completed.');
}
/**
* 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

@ -119,8 +119,8 @@ class GoogleAdsAssetService extends BaseService
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)
(asset_id, customer_id, asset_type, asset_name, resource_name, asset_url, status, asset_source,metadata, create_at, update_at)
VALUES (:asset_id, :customer_id, :asset_type, :asset_name, :resource_name, :asset_url, :status,:source, :metadata, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT (asset_id)
DO UPDATE SET
customer_id = EXCLUDED.customer_id,
@ -129,6 +129,7 @@ class GoogleAdsAssetService extends BaseService
resource_name = EXCLUDED.resource_name,
asset_url = EXCLUDED.asset_url,
status = EXCLUDED.status,
asset_source = EXCLUDED.asset_source,
metadata = EXCLUDED.metadata,
update_at = CURRENT_TIMESTAMP"; // update_at 使用 CURRENT_TIMESTAMP 自动更新
@ -204,6 +205,7 @@ class GoogleAdsAssetService extends BaseService
$resourceName['status'] = 2; //未定义,先占坑
$resourceName['resource_name'] = $googleAdsRow->getAsset()->getResourceName();
$resourceName['source'] = $googleAdsRow->getAsset()->getSource();
$resourceName['customer_id'] = $googleAdsRow->getCustomer()->getId();
$resourceNames[] = $resourceName;

View File

@ -7,6 +7,7 @@ use app\event\GoogleAdsCampaigns;
use app\event\GoogleAdsGroups;
use app\event\GoogleAdsAds;
use app\event\GoogleAdsAssets;
use app\event\GoogleAdsAssetRelations;
use app\event\GoogleAdsDateDatas;
@ -33,6 +34,12 @@ return [
GoogleAdsAssets::type => [
[GoogleAdsAssets::class, 'getAssets'],
],
GoogleAdsAssetRelations::IMAGEASSET => [
[GoogleAdsAssetRelations::class, 'getAssetRelations'],
],
GoogleAdsAssetRelations::VIDEOASSET => [
[GoogleAdsAssetRelations::class, 'getVideoAssetRelations'],
],
];

View File

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