314 lines
18 KiB
Markdown
314 lines
18 KiB
Markdown
# 架构设计文档
|
|
|
|
## 1. 概述
|
|
|
|
Reporting Service 采用 **领域驱动设计 (DDD)** 结合 **六边形架构 (Hexagonal Architecture)** 模式构建,实现业务逻辑与技术实现的解耦。
|
|
|
|
## 2. 架构图
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ API Layer (端口) │
|
|
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────────┐ │
|
|
│ │ ReportController│ │ ExportController│ │ HealthController │ │
|
|
│ └────────┬────────┘ └────────┬────────┘ └─────────────────────────────┘ │
|
|
│ │ │ │
|
|
│ ▼ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────────────┐│
|
|
│ │ Application Layer ││
|
|
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────────────┐ ││
|
|
│ │ │ Commands/Queries │ │ Schedulers │ │ Application Services │ ││
|
|
│ │ │ - GenerateReport │ │ - CronJobs │ │ - ReportingAppService │ ││
|
|
│ │ │ - ExportReport │ │ - Periodic Tasks │ │ │ ││
|
|
│ │ └────────┬─────────┘ └──────────────────┘ └────────────────────────┘ ││
|
|
│ └───────────┼──────────────────────────────────────────────────────────────┘│
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────────────┐│
|
|
│ │ Domain Layer (核心) ││
|
|
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────────────┐ ││
|
|
│ │ │ Aggregates │ │ Entities │ │ Value Objects │ ││
|
|
│ │ │ - ReportDefinit. │ │ - ReportFile │ │ - DateRange │ ││
|
|
│ │ │ - ReportSnapshot │ │ - AnalyticsMetric│ │ - ReportPeriod │ ││
|
|
│ │ └──────────────────┘ └──────────────────┘ │ - SnapshotData │ ││
|
|
│ │ └────────────────────────┘ ││
|
|
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────────────┐ ││
|
|
│ │ │ Domain Events │ │ Domain Services │ │ Repository Ports │ ││
|
|
│ │ │ - SnapshotCreated│ │ - ReportGenSvc │ │ (Interfaces Only) │ ││
|
|
│ │ │ - ReportExported │ │ │ │ │ ││
|
|
│ │ └──────────────────┘ └──────────────────┘ └────────────────────────┘ ││
|
|
│ └─────────────────────────────────────────────────────────────────────────┘│
|
|
│ ▲ │
|
|
│ │ │
|
|
│ ┌─────────────────────────────────────────────────────────────────────────┐│
|
|
│ │ Infrastructure Layer (适配器) ││
|
|
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────────────┐ ││
|
|
│ │ │ Persistence │ │ External APIs │ │ Export Services │ ││
|
|
│ │ │ - Prisma/PG │ │ - LeaderboardSvc │ │ - ExcelExport │ ││
|
|
│ │ │ - Repositories │ │ - PlantingSvc │ │ - CSVExport │ ││
|
|
│ │ │ - Mappers │ │ │ │ - PDFExport │ ││
|
|
│ │ └──────────────────┘ └──────────────────┘ └────────────────────────┘ ││
|
|
│ │ ┌──────────────────┐ ┌──────────────────┐ ││
|
|
│ │ │ Redis Cache │ │ Kafka │ ││
|
|
│ │ │ - ReportCache │ │ - EventPublisher │ ││
|
|
│ │ └──────────────────┘ └──────────────────┘ ││
|
|
│ └─────────────────────────────────────────────────────────────────────────┘│
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## 3. 目录结构
|
|
|
|
```
|
|
src/
|
|
├── api/ # API层 - 入站端口
|
|
│ ├── controllers/ # HTTP控制器
|
|
│ │ ├── report.controller.ts # 报表API
|
|
│ │ ├── export.controller.ts # 导出API
|
|
│ │ └── health.controller.ts # 健康检查
|
|
│ └── dto/ # 数据传输对象
|
|
│ ├── request/ # 请求DTO
|
|
│ └── response/ # 响应DTO
|
|
│
|
|
├── application/ # 应用层 - 用例编排
|
|
│ ├── commands/ # 命令处理器
|
|
│ │ ├── generate-report/ # 生成报表命令
|
|
│ │ └── export-report/ # 导出报表命令
|
|
│ ├── queries/ # 查询处理器
|
|
│ │ └── get-report-snapshot/ # 获取快照查询
|
|
│ ├── schedulers/ # 定时任务
|
|
│ │ └── report-generation.scheduler.ts
|
|
│ └── services/ # 应用服务
|
|
│ └── reporting-application.service.ts
|
|
│
|
|
├── domain/ # 领域层 - 业务核心
|
|
│ ├── aggregates/ # 聚合根
|
|
│ │ ├── report-definition/ # 报表定义聚合
|
|
│ │ └── report-snapshot/ # 报表快照聚合
|
|
│ ├── entities/ # 实体
|
|
│ │ ├── report-file.entity.ts
|
|
│ │ └── analytics-metric.entity.ts
|
|
│ ├── value-objects/ # 值对象
|
|
│ │ ├── date-range.vo.ts
|
|
│ │ ├── report-period.enum.ts
|
|
│ │ ├── report-type.enum.ts
|
|
│ │ └── snapshot-data.vo.ts
|
|
│ ├── events/ # 领域事件
|
|
│ │ ├── snapshot-created.event.ts
|
|
│ │ └── report-exported.event.ts
|
|
│ ├── repositories/ # 仓储接口
|
|
│ │ ├── report-definition.repository.interface.ts
|
|
│ │ └── report-snapshot.repository.interface.ts
|
|
│ └── services/ # 领域服务
|
|
│ └── report-generation.service.ts
|
|
│
|
|
├── infrastructure/ # 基础设施层 - 出站适配器
|
|
│ ├── persistence/ # 持久化
|
|
│ │ ├── prisma/ # Prisma配置
|
|
│ │ ├── repositories/ # 仓储实现
|
|
│ │ └── mappers/ # 对象映射器
|
|
│ ├── external/ # 外部服务客户端
|
|
│ │ ├── leaderboard-service/
|
|
│ │ └── planting-service/
|
|
│ ├── export/ # 导出服务实现
|
|
│ │ ├── excel-export.service.ts
|
|
│ │ ├── csv-export.service.ts
|
|
│ │ └── pdf-export.service.ts
|
|
│ └── redis/ # Redis缓存
|
|
│ └── report-cache.service.ts
|
|
│
|
|
├── shared/ # 共享模块
|
|
│ ├── decorators/ # 自定义装饰器
|
|
│ ├── filters/ # 异常过滤器
|
|
│ ├── guards/ # 守卫
|
|
│ ├── interceptors/ # 拦截器
|
|
│ └── strategies/ # Passport策略
|
|
│
|
|
└── config/ # 配置
|
|
├── app.config.ts
|
|
├── database.config.ts
|
|
└── redis.config.ts
|
|
```
|
|
|
|
## 4. 核心设计原则
|
|
|
|
### 4.1 依赖倒置原则 (DIP)
|
|
|
|
```typescript
|
|
// 领域层定义接口 (端口)
|
|
export interface IReportSnapshotRepository {
|
|
save(snapshot: ReportSnapshot): Promise<ReportSnapshot>;
|
|
findById(id: bigint): Promise<ReportSnapshot | null>;
|
|
findByCodeAndPeriodKey(code: string, periodKey: string): Promise<ReportSnapshot | null>;
|
|
}
|
|
|
|
// 基础设施层实现接口 (适配器)
|
|
@Injectable()
|
|
export class ReportSnapshotRepository implements IReportSnapshotRepository {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
async save(snapshot: ReportSnapshot): Promise<ReportSnapshot> {
|
|
// Prisma实现细节
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4.2 聚合根设计
|
|
|
|
```typescript
|
|
// ReportSnapshot 聚合根
|
|
export class ReportSnapshot extends AggregateRoot {
|
|
private readonly _id: bigint;
|
|
private _reportType: ReportType;
|
|
private _reportCode: string;
|
|
private _reportPeriod: ReportPeriod;
|
|
private _snapshotData: SnapshotData;
|
|
private _events: DomainEvent[] = [];
|
|
|
|
// 工厂方法 - 创建新快照
|
|
public static create(props: CreateSnapshotProps): ReportSnapshot {
|
|
const snapshot = new ReportSnapshot(props);
|
|
snapshot.addDomainEvent(new SnapshotCreatedEvent(snapshot));
|
|
return snapshot;
|
|
}
|
|
|
|
// 从持久化重建 - 不触发事件
|
|
public static reconstitute(props: ReconstitutionProps): ReportSnapshot {
|
|
return new ReportSnapshot(props);
|
|
}
|
|
|
|
// 业务行为
|
|
public updateData(newData: SnapshotData): void {
|
|
this.validateDataUpdate(newData);
|
|
this._snapshotData = newData;
|
|
this._updatedAt = new Date();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4.3 值对象不可变性
|
|
|
|
```typescript
|
|
// DateRange 值对象
|
|
export class DateRange {
|
|
private readonly _startDate: Date;
|
|
private readonly _endDate: Date;
|
|
|
|
private constructor(startDate: Date, endDate: Date) {
|
|
this._startDate = startDate;
|
|
this._endDate = endDate;
|
|
Object.freeze(this);
|
|
}
|
|
|
|
public static create(startDate: Date, endDate: Date): DateRange {
|
|
if (startDate > endDate) {
|
|
throw new DomainException('Start date must be before end date');
|
|
}
|
|
return new DateRange(startDate, endDate);
|
|
}
|
|
|
|
public equals(other: DateRange): boolean {
|
|
return this._startDate.getTime() === other._startDate.getTime() &&
|
|
this._endDate.getTime() === other._endDate.getTime();
|
|
}
|
|
}
|
|
```
|
|
|
|
## 5. 数据流
|
|
|
|
### 5.1 报表生成流程
|
|
|
|
```
|
|
┌─────────┐ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
|
|
│ Client │───▶│ ReportCtrl │───▶│ GenerateHandler │───▶│ DomainSvc │
|
|
└─────────┘ └──────────────┘ └─────────────────┘ └──────┬───────┘
|
|
│
|
|
┌─────────────────────────────────────────────┘
|
|
▼
|
|
┌──────────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
|
│ External Services │───▶│ ReportSnapshot │───▶│ Repository │
|
|
│ (Leaderboard/Plant.) │ │ (Aggregate) │ │ (Save to DB) │
|
|
└──────────────────────┘ └──────────────────┘ └──────────────────┘
|
|
```
|
|
|
|
### 5.2 报表导出流程
|
|
|
|
```
|
|
┌─────────┐ ┌──────────────┐ ┌─────────────────┐
|
|
│ Client │───▶│ ExportCtrl │───▶│ ExportHandler │
|
|
└─────────┘ └──────────────┘ └────────┬────────┘
|
|
│
|
|
┌───────────────────────────────────┴───────────────────────────┐
|
|
▼ ▼ ▼ ▼
|
|
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
|
│ ExcelExportSvc │ │ CSVExportSvc │ │ PDFExportSvc │
|
|
│ (ExcelJS) │ │ (csv-stringify) │ │ (PDFKit) │
|
|
└──────────────────┘ └──────────────────┘ └──────────────────┘
|
|
│ │ │
|
|
└───────────────────────┴────────────────────────┘
|
|
▼
|
|
┌──────────────────────┐
|
|
│ ReportFile Entity │
|
|
│ (Save to Storage) │
|
|
└──────────────────────┘
|
|
```
|
|
|
|
## 6. 关键组件
|
|
|
|
### 6.1 报表类型 (ReportType)
|
|
|
|
| 类型 | 描述 | 数据来源 |
|
|
|------|------|----------|
|
|
| LEADERBOARD_REPORT | 排行榜报表 | Leaderboard Service |
|
|
| PLANTING_REPORT | 种植报表 | Planting Service |
|
|
| COMMUNITY_REPORT | 社区报表 | Community Service |
|
|
| SYSTEM_ACCOUNT_REPORT | 系统账户报表 | Account Service |
|
|
| ANALYTICS_DASHBOARD | 分析仪表板 | 多数据源聚合 |
|
|
|
|
### 6.2 报表周期 (ReportPeriod)
|
|
|
|
| 周期 | 描述 | 用途 |
|
|
|------|------|------|
|
|
| DAILY | 日报 | 每日运营数据 |
|
|
| WEEKLY | 周报 | 周度趋势分析 |
|
|
| MONTHLY | 月报 | 月度业绩汇总 |
|
|
| QUARTERLY | 季报 | 季度财务报表 |
|
|
| YEARLY | 年报 | 年度总结 |
|
|
| CUSTOM | 自定义 | 灵活时间范围 |
|
|
|
|
### 6.3 导出格式 (OutputFormat)
|
|
|
|
| 格式 | 实现库 | 特点 |
|
|
|------|--------|------|
|
|
| EXCEL | ExcelJS | 支持样式、图表、多Sheet |
|
|
| CSV | csv-stringify | 轻量、通用 |
|
|
| PDF | PDFKit | 适合打印、分发 |
|
|
| JSON | 内置 | API集成、数据交换 |
|
|
|
|
## 7. 扩展点
|
|
|
|
### 7.1 添加新报表类型
|
|
|
|
1. 在 `ReportType` 枚举中添加新类型
|
|
2. 创建对应的外部服务客户端 (如需要)
|
|
3. 在 `GenerateReportHandler` 中添加数据获取逻辑
|
|
4. 更新 `ReportDefinition` 种子数据
|
|
|
|
### 7.2 添加新导出格式
|
|
|
|
1. 在 `OutputFormat` 枚举中添加新格式
|
|
2. 创建新的导出服务 (实现 `IExportService` 接口)
|
|
3. 在 `ExportReportHandler` 中注册新服务
|
|
|
|
### 7.3 集成新数据源
|
|
|
|
1. 在 `infrastructure/external/` 下创建服务客户端
|
|
2. 定义数据传输接口
|
|
3. 在应用层注入并使用
|
|
|
|
## 8. 安全考虑
|
|
|
|
- **认证**: JWT Bearer Token (可配置)
|
|
- **授权**: 基于角色的访问控制 (RBAC)
|
|
- **数据脱敏**: 敏感数据在导出时进行脱敏处理
|
|
- **审计日志**: 所有报表操作记录到审计表
|