目录
完整目录树
app/
├─ controller/
│ └─ api/
│ └─ v1/
│ ├─ ReportController.php # 报表接口
│ ├─ DashboardController.php # 仪表盘接口
│ ├─ MetricController.php # 指标接口
│ └─ ExportController.php # 导出接口
│
├─ model/
│ └─ eloquent/
│ ├─ Report.php # 报表模型
│ ├─ Metric.php # 指标模型
│ ├─ DataSource.php # 数据源模型
│ └─ ReportSchedule.php # 报表调度
│
├─ middleware/
│ ├─ auth/
│ │ └─ ReportAccessMiddleware.php # 报表访问权限
│ └─ cache/
│ └─ ReportCacheMiddleware.php # 报表缓存
│
├─ process/
│ ├─ task/
│ │ ├─ MetricAggregationTask.php # 指标聚合任务
│ │ ├─ ReportGenerationTask.php # 报表生成任务
│ │ └─ DataSyncTask.php # 数据同步任务
│ └─ queue/
│ └─ ExportQueue.php # 导出队列
│
├─ service/
│ ├─ report/
│ │ ├─ GenerateReportService.php # 生成报表
│ │ ├─ ScheduleReportService.php # 调度报表
│ │ └─ ExportReportService.php # 导出报表
│ ├─ metric/
│ │ ├─ CalculateMetricService.php # 计算指标
│ │ ├─ AggregateMetricService.php # 聚合指标
│ │ └─ CompareMetricService.php # 对比指标
│ ├─ dashboard/
│ │ ├─ BuildDashboardService.php # 构建仪表盘
│ │ └─ RefreshDashboardService.php # 刷新仪表盘
│ └─ export/
│ ├─ ExportToCsvService.php # 导出 CSV
│ ├─ ExportToExcelService.php # 导出 Excel
│ └─ ExportToPdfService.php # 导出 PDF
│
├─ domain/
│ ├─ report/
│ │ ├─ entity/
│ │ │ ├─ Report.php # 报表实体
│ │ │ ├─ ReportSection.php # 报表章节
│ │ │ └─ ReportSchedule.php # 报表调度
│ │ ├─ enum/ # 枚举
│ │ │ ├─ ReportType.php # 报表类型枚举
│ │ │ ├─ ReportFormat.php # 报表格式枚举
│ │ │ └─ ReportStatus.php # 报表状态枚举
│ │ ├─ vo/ # 值对象
│ │ │ └─ DateRange.php # 日期范围
│ │ └─ rule/
│ │ └─ ReportGenerationRule.php # 报表生成规则
│ │
│ ├─ metric/
│ │ ├─ entity/
│ │ │ ├─ Metric.php # 指标实体
│ │ │ └─ MetricSnapshot.php # 指标快照
│ │ ├─ enum/ # 枚举
│ │ │ ├─ MetricType.php # 指标类型枚举
│ │ │ └─ AggregationType.php # 聚合类型枚举
│ │ ├─ vo/ # 值对象
│ │ │ ├─ MetricValue.php # 指标值
│ │ │ └─ Dimension.php # 维度
│ │ └─ rule/
│ │ ├─ MetricCalculationRule.php # 指标计算规则
│ │ └─ MetricValidationRule.php # 指标验证规则
│ │
│ └─ datasource/
│ ├─ entity/
│ │ └─ DataSource.php # 数据源实体
│ ├─ enum/ # 枚举
│ │ └─ DataSourceType.php # 数据源类型枚举
│ ├─ vo/ # 值对象
│ │ └─ QueryConfig.php # 查询配置
│ └─ rule/
│ └─ DataAccessRule.php # 数据访问规则
│
├─ contract/
│ ├─ repository/
│ │ ├─ ReportRepositoryInterface.php
│ │ ├─ MetricRepositoryInterface.php
│ │ └─ DataSourceRepositoryInterface.php
│ ├─ gateway/
│ │ └─ DataWarehouseGatewayInterface.php # 数据仓库网关
│ └─ service/
│ ├─ ExportServiceInterface.php
│ └─ AggregationServiceInterface.php
│
├─ infrastructure/
│ ├─ repository/
│ │ ├─ eloquent/
│ │ │ ├─ EloquentReportRepository.php
│ │ │ └─ EloquentMetricRepository.php
│ │ └─ redis/
│ │ └─ RedisMetricCacheRepository.php # 指标缓存
│ │
│ ├─ gateway/
│ │ ├─ clickhouse/
│ │ │ └─ ClickHouseDataWarehouse.php # ClickHouse 数据仓库
│ │ └─ bigquery/
│ │ └─ BigQueryDataWarehouse.php # BigQuery 数据仓库
│ │
│ └─ export/
│ ├─ CsvExporter.php # CSV 导出器
│ ├─ ExcelExporter.php # Excel 导出器
│ └─ PdfExporter.php # PDF 导出器
│
└─ support/
├─ helper/
│ ├─ chart_helper.php # 图表辅助函数
│ └─ date_helper.php # 日期辅助函数
└─ exception/
├─ ReportException.php
└─ MetricException.php模块划分
核心模块
- 报表模块 (Report) - 报表生成、调度、导出
- 指标模块 (Metric) - 指标计算、聚合、对比
- 仪表盘模块 (Dashboard) - 实时数据展示
- 数据源模块 (DataSource) - 多数据源接入
- 导出模块 (Export) - CSV/Excel/PDF 导出
目录职责
app/service/report/
职责: 报表业务编排 - 生成报表流程、调度报表、导出报表
app/service/metric/
职责: 指标业务编排 - 计算指标、聚合数据、对比分析
app/domain/report/
职责: 报表领域逻辑 - 报表实体、报表类型、生成规则
app/domain/metric/
职责: 指标领域逻辑 - 指标实体、计算规则、聚合类型
app/infrastructure/gateway/
职责: 数据仓库适配 - ClickHouse、BigQuery 等数据仓库接入
app/process/task/
职责: 后台任务 - 定时聚合指标、生成报表、同步数据
关键代码示例
1. 指标实体
php
<?php
declare(strict_types=1);
namespace app\domain\metric\entity;
use app\domain\metric\enum\MetricType;
use app\domain\metric\vo\MetricValue;
use app\domain\metric\vo\Dimension;
/**
* 指标实体
*/
final class Metric
{
private function __construct(
private readonly int $id,
private readonly string $name,
private readonly MetricType $type,
private readonly MetricValue $value,
private readonly array $dimensions,
private readonly \DateTimeImmutable $timestamp
) {
}
public static function create(
string $name,
MetricType $type,
MetricValue $value,
array $dimensions = []
): self {
return new self(
id: 0,
name: $name,
type: $type,
value: $value,
dimensions: $dimensions,
timestamp: new \DateTimeImmutable()
);
}
public function withDimension(Dimension $dimension): self
{
$dimensions = $this->dimensions;
$dimensions[] = $dimension;
return new self(
id: $this->id,
name: $this->name,
type: $this->type,
value: $this->value,
dimensions: $dimensions,
timestamp: $this->timestamp
);
}
// Getters
public function id(): int
{
return $this->id;
}
public function name(): string
{
return $this->name;
}
public function type(): MetricType
{
return $this->type;
}
public function value(): MetricValue
{
return $this->value;
}
public function dimensions(): array
{
return $this->dimensions;
}
public function timestamp(): \DateTimeImmutable
{
return $this->timestamp;
}
}2. 计算指标服务
php
<?php
declare(strict_types=1);
namespace app\service\metric;
use app\contract\repository\MetricRepositoryInterface;
use app\contract\gateway\DataWarehouseGatewayInterface;
use app\domain\metric\entity\Metric;
use app\domain\metric\enum\MetricType;
use app\domain\metric\vo\MetricValue;
use app\domain\report\vo\DateRange;
/**
* 计算指标服务
*/
final class CalculateMetricService
{
public function __construct(
private readonly MetricRepositoryInterface $metricRepository,
private readonly DataWarehouseGatewayInterface $dataWarehouse
) {
}
public function handle(
string $metricName,
MetricType $type,
DateRange $dateRange,
array $filters = []
): Metric {
// 1. 从数据仓库查询原始数据
$rawData = $this->dataWarehouse->query(
metric: $metricName,
dateRange: $dateRange,
filters: $filters
);
// 2. 根据指标类型计算
$value = match ($type->value()) {
'sum' => $this->calculateSum($rawData),
'avg' => $this->calculateAverage($rawData),
'count' => $this->calculateCount($rawData),
'max' => $this->calculateMax($rawData),
'min' => $this->calculateMin($rawData),
default => throw new \InvalidArgumentException('Unknown metric type'),
};
// 3. 创建指标实体
$metric = Metric::create(
name: $metricName,
type: $type,
value: MetricValue::fromFloat($value)
);
// 4. 持久化
$this->metricRepository->save($metric);
return $metric;
}
private function calculateSum(array $data): float
{
return array_sum(array_column($data, 'value'));
}
private function calculateAverage(array $data): float
{
$sum = $this->calculateSum($data);
$count = count($data);
return $count > 0 ? $sum / $count : 0.0;
}
private function calculateCount(array $data): float
{
return (float) count($data);
}
private function calculateMax(array $data): float
{
$values = array_column($data, 'value');
return !empty($values) ? max($values) : 0.0;
}
private function calculateMin(array $data): float
{
$values = array_column($data, 'value');
return !empty($values) ? min($values) : 0.0;
}
}3. 生成报表服务
php
<?php
declare(strict_types=1);
namespace app\service\report;
use app\contract\repository\ReportRepositoryInterface;
use app\service\metric\CalculateMetricService;
use app\domain\report\entity\Report;
use app\domain\report\enum\ReportType;
use app\domain\report\vo\DateRange;
use support\Db;
/**
* 生成报表服务
*/
final class GenerateReportService
{
public function __construct(
private readonly ReportRepositoryInterface $reportRepository,
private readonly CalculateMetricService $calculateMetricService
) {
}
public function handle(
string $reportName,
ReportType $type,
DateRange $dateRange,
array $metricNames = []
): Report {
return Db::transaction(function () use (
$reportName,
$type,
$dateRange,
$metricNames
) {
// 1. 创建报表实体
$report = Report::create(
name: $reportName,
type: $type,
dateRange: $dateRange
);
// 2. 计算所有指标
foreach ($metricNames as $metricName) {
$metric = $this->calculateMetricService->handle(
metricName: $metricName,
type: $this->getMetricType($metricName),
dateRange: $dateRange
);
$report->addMetric($metric);
}
// 3. 标记为已完成
$report->markAsCompleted();
// 4. 持久化
$this->reportRepository->save($report);
return $report;
});
}
private function getMetricType(string $metricName): \app\domain\metric\values\MetricType
{
// 根据指标名称返回对应的计算类型
return \app\domain\metric\values\MetricType::sum();
}
}4. ClickHouse 数据仓库网关
php
<?php
declare(strict_types=1);
namespace app\infrastructure\gateway\clickhouse;
use app\contract\gateway\DataWarehouseGatewayInterface;
use app\domain\metric\vo\DateRange;
/**
* ClickHouse 数据仓库网关
*/
final class ClickHouseDataWarehouse implements DataWarehouseGatewayInterface
{
public function __construct(
private readonly string $host,
private readonly int $port,
private readonly string $database
) {
}
public function query(
string $metric,
DateRange $dateRange,
array $filters = []
): array {
// 1. 构建 SQL 查询
$sql = $this->buildQuery($metric, $dateRange, $filters);
// 2. 执行查询
$result = $this->executeQuery($sql);
return $result;
}
private function buildQuery(
string $metric,
DateRange $dateRange,
array $filters
): string {
$table = $this->getTableName($metric);
$where = $this->buildWhereClause($dateRange, $filters);
return "SELECT * FROM {$table} WHERE {$where}";
}
private function buildWhereClause(DateRange $dateRange, array $filters): string
{
$conditions = [];
// 日期范围条件
$conditions[] = sprintf(
"date >= '%s' AND date <= '%s'",
$dateRange->start()->format('Y-m-d'),
$dateRange->end()->format('Y-m-d')
);
// 其他过滤条件
foreach ($filters as $field => $value) {
$conditions[] = sprintf("%s = '%s'", $field, $value);
}
return implode(' AND ', $conditions);
}
private function executeQuery(string $sql): array
{
// 执行 ClickHouse 查询
return [];
}
private function getTableName(string $metric): string
{
return 'metrics_' . $metric;
}
}5. 指标聚合任务
php
<?php
declare(strict_types=1);
namespace app\process\task;
use app\service\metric\AggregateMetricService;
use app\domain\metric\values\AggregationType;
use app\domain\metric\vo\DateRange;
use Workerman\Timer;
/**
* 指标聚合任务
*/
final class MetricAggregationTask
{
public function __construct(
private readonly AggregateMetricService $aggregateMetricService
) {
}
public function onWorkerStart(): void
{
// 每小时聚合一次指标
Timer::add(3600, function () {
$this->aggregateHourlyMetrics();
});
// 每天凌晨聚合昨天的指标
Timer::add(86400, function () {
$this->aggregateDailyMetrics();
});
}
private function aggregateHourlyMetrics(): void
{
try {
$dateRange = DateRange::lastHour();
$this->aggregateMetricService->handle(
metricName: 'revenue',
aggregationType: AggregationType::sum(),
dateRange: $dateRange,
groupBy: 'hour'
);
logger()->info('Hourly metrics aggregated successfully');
} catch (\Exception $e) {
logger()->error('Hourly metric aggregation failed', [
'error' => $e->getMessage(),
]);
}
}
private function aggregateDailyMetrics(): void
{
try {
$dateRange = DateRange::yesterday();
$this->aggregateMetricService->handle(
metricName: 'revenue',
aggregationType: AggregationType::sum(),
dateRange: $dateRange,
groupBy: 'day'
);
logger()->info('Daily metrics aggregated successfully');
} catch (\Exception $e) {
logger()->error('Daily metric aggregation failed', [
'error' => $e->getMessage(),
]);
}
}
}最佳实践
数据聚合策略
- 预聚合: 定时任务预先聚合常用指标,提升查询速度
- 分层聚合: 小时 → 天 → 周 → 月,逐层聚合
- 增量计算: 只计算新增数据,避免全量计算
- 缓存策略: 使用 Redis 缓存热点指标
性能优化
- 数据仓库: 使用 ClickHouse 等列式数据库存储分析数据
- 异步导出: 大报表使用队列异步导出
- 分页查询: 大数据集分页返回
- 索引优化: 在时间字段和常用维度上建立索引
报表调度
- 定时生成: 使用 Cron 表达式配置报表生成时间
- 邮件推送: 报表生成后自动发送邮件
- 增量更新: 只更新变化的数据
- 失败重试: 生成失败自动重试
数据安全
- 权限控制: 基于角色的报表访问权限
- 数据脱敏: 敏感数据脱敏展示
- 审计日志: 记录所有报表访问和导出操作
- 导出限制: 限制导出数据量和频率