diff --git a/app/controller/AdController.php b/app/controller/AdController.php index 5f1b4a4..f1bdd01 100644 --- a/app/controller/AdController.php +++ b/app/controller/AdController.php @@ -28,6 +28,7 @@ class AdController $result = $this->googleAdsReportService::getAdList($page, $pageSize, $keyword, $dateRange); return $this->successResponse($result); } + public function listCampaigns(Request $request) { // 获取请求参数 @@ -41,6 +42,33 @@ class AdController return $this->successResponse($result); } + public function exportAdsToExcel(Request $request) + { + $keyword = $request->input('keyword', ''); // 获取关键字参数 + $dateRange = $request->input('date_range', ''); // 获取日期范围参数 + + // 调用 service 层导出数据 + return $this->googleAdsReportService::exportAdListToExcel($keyword, $dateRange); + } + + public function exportCampaignsToExcel(Request $request) + { + $keyword = $request->input('keyword', ''); // 获取关键字参数 + $dateRange = $request->input('date_range', ''); // 获取日期范围参数 + + // 调用 service 层导出数据 + return $this->googleAdsReportService::exportCampaignsToExcel($keyword, $dateRange); + } + + public function exportGroupsToExcel(Request $request) + { + $keyword = $request->input('keyword', ''); // 获取关键字参数 + $dateRange = $request->input('date_range', ''); // 获取日期范围参数 + + // 调用 service 层导出数据 + return $this->googleAdsReportService::exportAdGroupsToExcel($keyword, $dateRange); + } + public function listGroups(Request $request) { // 获取请求参数 diff --git a/app/service/GoogleAdsReportService.php b/app/service/GoogleAdsReportService.php index 4393325..3ab8c2a 100644 --- a/app/service/GoogleAdsReportService.php +++ b/app/service/GoogleAdsReportService.php @@ -6,6 +6,8 @@ use app\model\Ad; use app\model\DayData; use app\model\Campaign; use app\model\AdGroup; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx; use think\db\exception\DbException; use think\facade\Db; use think\model\Collection; @@ -64,6 +66,99 @@ class GoogleAdsReportService ]; } + /** + * 导出广告列表到 Excel + * + * @param string $keyword + * @param string $dateRange + * @return void + */ + public static function exportAdListToExcel($keyword, $dateRange) + { + // 获取所有的广告数据 + $query = Ad::alias('a') + ->leftJoin('bps_google_ad_day_data d', 'a.ad_id = d.ad_id') + ->field('a.ad_id, a.ad_name, a.status as ad_status, a.customer_id, + SUM(d.clicks) as clicks, SUM(d.cost_micros) as cost_micros, + SUM(d.conversions) as conversions, SUM(d.conversions_value) as conversions_value, + SUM(d.impressions) as impressions') + ->group('a.ad_id') + ->where(function ($query) use ($keyword) { + if ($keyword) { + $query->where('a.ad_name', 'like', '%' . $keyword . '%'); + } + }); + + // 根据日期维度添加聚合条件 + switch ($dateRange) { + case 'Today': + $query->where('d.date', '=', date('Y-m-d')); + break; + case 'Yesterday': + $query->where('d.date', '=', date('Y-m-d', strtotime('-1 day'))); + break; + case 'Last Week': + $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 week'))); + break; + case 'Last Month': + $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 month'))); + break; + case 'Last Year': + $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 year'))); + break; + default: + break; + } + + // 获取所有广告数据 + $ads = $query->select(); + + // 创建一个新的 Spreadsheet 对象 + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // 设置表头 + $sheet->setCellValue('A1', 'Ad ID'); + $sheet->setCellValue('B1', 'Ad Name'); + $sheet->setCellValue('C1', 'Customer ID'); + $sheet->setCellValue('D1', 'Ad Status'); + $sheet->setCellValue('E1', 'Clicks'); + $sheet->setCellValue('F1', 'Cost Micros'); + $sheet->setCellValue('G1', 'Conversions'); + $sheet->setCellValue('H1', 'Conversions Value'); + $sheet->setCellValue('I1', 'Impressions'); + + // 填充数据 + $row = 2; // 从第二行开始 + foreach ($ads as $ad) { + $sheet->setCellValueExplicit('A' . $row, (string)$ad->ad_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // 设置 ad_id 为文本 + $sheet->setCellValueExplicit('B' . $row, $ad->ad_name); // 直接设置 ad_name + $sheet->setCellValueExplicit('C' . $row, (string)$ad->customer_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // 设置 customer_id 为文本 + $sheet->setCellValue('D' . $row, $ad->ad_status); + $sheet->setCellValue('E' . $row, $ad->clicks); + $sheet->setCellValue('F' . $row, $ad->cost_micros); + $sheet->setCellValue('G' . $row, $ad->conversions); + $sheet->setCellValue('H' . $row, $ad->conversions_value); + $sheet->setCellValue('I' . $row, $ad->impressions); + $row++; + } + + // 设置 Excel 文件名 + $fileName = 'Ad_Report_' . $dateRange . '_' . date('Y-m-d_H-i-s') . '.xlsx'; + + // 创建 Excel 文件并保存 + $writer = new Xlsx($spreadsheet); + $filePath = public_path() . '/' . $fileName; + + try { + $writer->save($filePath); + return response()->download($filePath, $fileName); +// return ['success' => true, 'file_path' => $filePath]; + } catch (\Exception $e) { + return ['' => false, 'message' => $e->getMessage()]; + } + } + /** * 获取广告系列列表 @@ -117,6 +212,101 @@ class GoogleAdsReportService ]; } + + /** + * 导出广告系列数据到 Excel + * + * @param string $keyword + * @param string $dateRange + * @return void + */ + public static function exportCampaignsToExcel($keyword, $dateRange) + { + // 获取所有的广告系列数据 + $query = Campaign::alias('c') + ->leftJoin('bps_google_ad_day_data d', 'c.campaign_id = d.campaign_id') + ->field('c.campaign_id, c.status as campaign_status, c.customer_id, + SUM(d.clicks) as clicks, SUM(d.cost_micros) as cost_micros, + SUM(d.conversions) as conversions, SUM(d.conversions_value) as conversions_value, + SUM(d.impressions) as impressions') + ->group('c.campaign_id') + ->where(function ($query) use ($keyword) { + if ($keyword) { + $query->where('c.campaign_name', 'like', '%' . $keyword . '%'); + } + }); + + // 根据日期维度添加聚合条件 + switch ($dateRange) { + case 'Today': + $query->where('d.date', '=', date('Y-m-d')); + break; + case 'Yesterday': + $query->where('d.date', '=', date('Y-m-d', strtotime('-1 day'))); + break; + case 'Last Week': + $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 week'))); + break; + case 'Last Month': + $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 month'))); + break; + case 'Last Year': + $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 year'))); + break; + default: + break; + } + + // 获取所有广告系列数据 + $campaigns = $query->select(); + + // 创建一个新的 Spreadsheet 对象 + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // 设置表头 + $sheet->setCellValue('A1', 'Campaign ID'); + $sheet->setCellValue('B1', 'Customer ID'); + $sheet->setCellValue('C1', 'Status'); + $sheet->setCellValue('D1', 'Clicks'); + $sheet->setCellValue('E1', 'Cost Micros'); + $sheet->setCellValue('F1', 'Conversions'); + $sheet->setCellValue('G1', 'Conversions Value'); + $sheet->setCellValue('H1', 'Impressions'); + + // 填充数据 + $row = 2; // 从第二行开始 + foreach ($campaigns as $campaign) { +//使用 setCellValueExplicit 显式设置为文本格式 + $sheet->setCellValueExplicit('A' . $row, (string)$campaign->campaign_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // 设置为文本格式 + $sheet->setCellValueExplicit('B' . $row, (string)$campaign->customer_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // 设置为文本格式 + $sheet->setCellValue('C' . $row, $campaign->campaign_status); + $sheet->setCellValue('D' . $row, $campaign->clicks); + $sheet->setCellValue('E' . $row, $campaign->cost_micros); + $sheet->setCellValue('F' . $row, $campaign->conversions); + $sheet->setCellValue('G' . $row, $campaign->conversions_value); + $sheet->setCellValue('H' . $row, $campaign->impressions); + $row++; + } + + // 设置 Excel 文件名 + $fileName = 'Campaign_Report_' . $dateRange . '_' . date('Y-m-d_H-i-s') . '.xlsx'; + + // 创建 Excel 文件并保存 + $writer = new Xlsx($spreadsheet); + $filePath = public_path() . '/' . $fileName; + + try { + $writer->save($filePath); + return response()->download($filePath, $fileName); +// return ['success' => true, 'file_path' => $filePath]; + } catch (\Exception $e) { + return ['' => false, 'message' => $e->getMessage()]; + } + + } + + /** * 获取广告系列列表 */ @@ -183,4 +373,100 @@ class GoogleAdsReportService } } + + /** + * 将广告组数据导出到 Excel 文件 + */ + public static function exportAdGroupsToExcel($keyword = '', $dateRange = 'Today') + { + // 初始化查询 + $query = AdGroup::alias('ag') + ->leftJoin('bps_google_ad_day_data d', 'ag.ad_group_id = d.ad_group_id') + ->field('ag.ad_group_id, ag.campaign_id, ag.customer_id, + SUM(d.clicks) as clicks, SUM(d.cost_micros) as cost_micros, + SUM(d.conversions) as conversions, SUM(d.conversions_value) as conversions_value, + SUM(d.impressions) as impressions') + ->group('ag.ad_group_id') + ->where(function ($query) use ($keyword) { + if ($keyword) { + $query->where('ag.ad_group_name', 'like', '%' . $keyword . '%'); + } + }); + + // 根据日期范围添加聚合条件 + switch ($dateRange) { + case 'Today': + $query->where('d.date', '=', date('Y-m-d')); + break; + case 'Yesterday': + $query->where('d.date', '=', date('Y-m-d', strtotime('-1 day'))); + break; + case 'Last Week': + $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 week'))); + break; + case 'Last Month': + $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 month'))); + break; + case 'Last Year': + $query->where('d.date', '>=', date('Y-m-d', strtotime('-1 year'))); + break; + default: + break; + } + // 获取所有广告组数据 + $ad_groups = $query->select(); + + + // 创建一个新的 Spreadsheet 对象 + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // 设置表头 + $sheet->setCellValue('A1', 'Ad Group ID') + ->setCellValue('B1', 'Campaign ID') + ->setCellValue('C1', 'Customer ID') + ->setCellValue('D1', 'Clicks') + ->setCellValue('E1', 'Cost Micros') + ->setCellValue('F1', 'Conversions') + ->setCellValue('G1', 'Conversions Value') + ->setCellValue('H1', 'Impressions'); + + // 填充数据 + $row = 2; + foreach ($ad_groups as $adGroup) { + // 使用 setCellValueExplicit 显式设置为文本格式 + $sheet->setCellValueExplicit('A' . $row, (string)$adGroup->ad_group_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // 设置为文本格式 + $sheet->setCellValueExplicit('B' . $row, (string)$adGroup->campaign_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); + $sheet->setCellValueExplicit('C' . $row, (string)$adGroup->customer_id, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); + $sheet->setCellValue('D' . $row, $adGroup->clicks); + $sheet->setCellValue('E' . $row, $adGroup->cost_micros); + $sheet->setCellValue('F' . $row, $adGroup->conversions); + $sheet->setCellValue('G' . $row, $adGroup->conversions_value); + $sheet->setCellValue('H' . $row, $adGroup->impressions); + $row++; + } + + + // 自动调整所有列宽 + foreach (range('A', 'H') as $column) { + $spreadsheet->getActiveSheet()->getColumnDimension($column)->setAutoSize(true); + } + + + // 设置 Excel 文件名 + $fileName = 'AdGroup_Report_' . $dateRange . '_' . date('Y-m-d_H-i-s') . '.xlsx'; + + // 创建 Excel 文件并保存 + $writer = new Xlsx($spreadsheet); + $filePath = public_path() . '/' . $fileName; + + try { + $writer->save($filePath); + return response()->download($filePath, $fileName); +// return ['success' => true, 'file_path' => $filePath]; + } catch (\Exception $e) { + return ['' => false, 'message' => $e->getMessage()]; + } + } + } diff --git a/composer.json b/composer.json index d5ea01b..99a5b6a 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,8 @@ "workerman/crontab": "^1.0", "illuminate/redis": "^10.48", "symfony/var-dumper": "^6.4", - "webman/think-orm": "^1.1" + "webman/think-orm": "^1.1", + "phpoffice/phpspreadsheet": "^3.6" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index 2241875..0fde35d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "95cf30222acc62ce0f01c3ff2f8696b0", + "content-hash": "69fc720415852f55d5319c4ab078fb79", "packages": [ { "name": "brick/math", @@ -1673,6 +1673,190 @@ }, "time": "2024-11-14T18:34:49+00:00" }, + { + "name": "maennchen/zipstream-php", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "6187e9cc4493da94b9b63eb2315821552015fca9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/6187e9cc4493da94b9b63eb2315821552015fca9", + "reference": "6187e9cc4493da94b9b63eb2315821552015fca9", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.1" + }, + "require-dev": { + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^10.0", + "vimeo/psalm": "^5.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2024-10-10T12:33:01+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, { "name": "monolog/monolog", "version": "2.10.0", @@ -2105,6 +2289,110 @@ }, "time": "2020-10-12T12:39:22+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "bce5db99872f9613121c3ad033c43318a3789396" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/bce5db99872f9613121c3ad033c43318a3789396", + "reference": "bce5db99872f9613121c3ad033c43318a3789396", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^8.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/3.6.0" + }, + "time": "2024-12-08T15:04:12+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.3", @@ -4023,5 +4311,8 @@ "php": ">=8.1" }, "platform-dev": {}, + "platform-overrides": { + "php": "8.1.0" + }, "plugin-api-version": "2.6.0" } diff --git a/config/route.php b/config/route.php index f3d5a03..0af3a3a 100644 --- a/config/route.php +++ b/config/route.php @@ -39,6 +39,15 @@ Route::group('/googleads', function () { Route::group('/adgroup', function () { Route::post('/list', [AdController::class, 'listGroups']); }); + Route::group('/ad', function () { + Route::post('/export', [AdController::class, 'exportAdsToExcel']); + }); + Route::group('/campaign', function () { + Route::post('/export', [AdController::class, 'exportCampaignsToExcel']); + }); + Route::group('/adgroup', function () { + Route::post('/export', [AdController::class, 'exportGroupsToExcel']); + }); }); Route::group('/campaign', function () {