feat: 添加 PHPSpreadsheet 支持以增强报表导出功能

- 在 `composer.json` 中新增 `phpoffice/phpspreadsheet` 依赖。
- 更新 `ReportJobDownloadController` 以使用 `AdminReportSpreadsheetExporter` 进行 XLSX 格式的报表导出,简化导出逻辑并确保文件名包含动态生成的输出路径后缀。
- 更新 `AdminAuthorizationRegistry` 中的权限定义,扩展相关权限以支持新的设置管理功能。
This commit is contained in:
2026-05-26 13:53:18 +08:00
parent bba084b3c1
commit 3c74ffc2d5
9 changed files with 538 additions and 75 deletions

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Reports;
use App\Models\ReportJob;
use App\Services\Admin\AdminReportJobService;
use App\Services\Admin\AdminReportQueryService;
use App\Services\Admin\AdminReportSpreadsheetExporter;
use Symfony\Component\HttpFoundation\StreamedResponse;
/** GET /api/v1/admin/report-jobs/{report_job}/download */
@@ -14,20 +15,24 @@ final class ReportJobDownloadController
ReportJob $report_job,
AdminReportJobService $service,
AdminReportQueryService $queryService,
AdminReportSpreadsheetExporter $spreadsheetExporter,
): StreamedResponse {
$filterJson = is_array($report_job->filter_json) ? $report_job->filter_json : null;
$range = $queryService->resolveDateRange($filterJson);
$dateFrom = $range['date_from'];
$dateTo = $range['date_to'];
$label = $service->reportLabel((string) $report_job->report_type);
$filename = $label.'_'.$dateFrom.'_'.$dateTo.'.'.$report_job->export_format;
$pathSuffix = $queryService->resolveOutputPathSuffix(
(string) $report_job->report_type,
$filterJson,
$dateFrom,
$dateTo,
);
$filename = $label.'_'.$pathSuffix.'.'.$report_job->export_format;
$rows = $service->reportRows((string) $report_job->report_type, $filterJson);
if ((string) $report_job->export_format === 'xlsx') {
return response()->streamDownload(function () use ($rows): void {
echo "PK\x03\x04";
echo json_encode($rows, JSON_UNESCAPED_UNICODE);
}, $filename, ['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']);
return $spreadsheetExporter->streamDownload($rows, $filename);
}
return response()->streamDownload(function () use ($rows): void {

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Services\Admin;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Symfony\Component\HttpFoundation\StreamedResponse;
/** 将 {@see AdminReportQueryService::reportRows} 的二维数组导出为真实 xlsx。 */
final class AdminReportSpreadsheetExporter
{
/**
* @param list<array<int, string|int|float|null>> $rows
*/
public function streamDownload(array $rows, string $filename): StreamedResponse
{
return response()->streamDownload(function () use ($rows): void {
$spreadsheet = new Spreadsheet;
$sheet = $spreadsheet->getActiveSheet();
foreach ($rows as $rowIndex => $row) {
foreach ($row as $colIndex => $cell) {
$sheet->setCellValue([$colIndex + 1, $rowIndex + 1], $cell ?? '');
}
}
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
$spreadsheet->disconnectWorksheets();
}, $filename, [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
]);
}
}

View File

@@ -380,8 +380,8 @@ final class AdminAuthorizationRegistry
['code' => 'admin.config.risk-cap-versions.items.replace', 'module_code' => 'config', 'name' => '替换封顶版本条目', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/config/risk-cap-versions/{id}/items', 'route_name' => 'api.v1.admin.config.risk-cap-versions.items.replace', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.risk_cap.manage']],
['code' => 'admin.config.risk-cap-versions.publish', 'module_code' => 'config', 'name' => '发布封顶版本', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/config/risk-cap-versions/{id}/publish', 'route_name' => 'api.v1.admin.config.risk-cap-versions.publish', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.risk_cap.manage']],
['code' => 'admin.config.risk-cap-versions.destroy', 'module_code' => 'config', 'name' => '删除封顶版本', 'http_method' => 'DELETE', 'uri_pattern' => '/api/v1/admin/config/risk-cap-versions/{id}', 'route_name' => 'api.v1.admin.config.risk-cap-versions.destroy', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.risk_cap.manage']],
['code' => 'admin.settings.index', 'module_code' => 'settings', 'name' => '系统设置列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/settings', 'route_name' => 'api.v1.admin.settings.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.wallet_reconcile.manage']],
['code' => 'admin.settings.update', 'module_code' => 'settings', 'name' => '系统设置更新', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/settings/{key}', 'route_name' => 'api.v1.admin.settings.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.wallet_reconcile.manage']],
['code' => 'admin.settings.index', 'module_code' => 'settings', 'name' => '系统设置列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/settings', 'route_name' => 'api.v1.admin.settings.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.wallet_reconcile.manage', 'prd.rebate.manage', 'prd.rebate.view', 'prd.payout.manage']],
['code' => 'admin.settings.update', 'module_code' => 'settings', 'name' => '系统设置更新', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/settings/{key}', 'route_name' => 'api.v1.admin.settings.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.wallet_reconcile.manage', 'prd.rebate.manage', 'prd.payout.manage']],
['code' => 'admin.currencies.index', 'module_code' => 'settings', 'name' => '币种列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/currencies', 'route_name' => 'api.v1.admin.currencies.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.currency.manage']],
['code' => 'admin.currencies.store', 'module_code' => 'settings', 'name' => '创建币种', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/currencies', 'route_name' => 'api.v1.admin.currencies.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.currency.manage']],
['code' => 'admin.currencies.update', 'module_code' => 'settings', 'name' => '更新币种', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/currencies/{currency}', 'route_name' => 'api.v1.admin.currencies.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.currency.manage']],