dv/docs/backend-architecture-guide.md

805 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DataViz Pro — 后端开发指南
## 一、技术栈
| 层面 | 选型 |
|------|------|
| 语言 | Python 3.12+ |
| Web 框架 | FastAPI |
| 数据库 | PostgreSQL 16 |
| ORM | SQLAlchemy 2.0 (async) |
| 数据库迁移 | Alembic |
| 文件解析 | openpyxl (xlsx) + xlrd (xls) + pandas (csv/json) |
| PDF 导出 | WeasyPrint / ReportLab |
| PPT 导出 | python-pptx |
| Excel 导出 | openpyxl |
| HTML 导出 | Jinja2 模板 + 内联 ECharts |
| 图片导出 | Playwright (服务端渲染 ECharts → 截图) |
| 容器化 | Docker + Docker Compose |
| 依赖管理 | Poetry |
| 代码质量 | Ruff (lint + format) + mypy (类型检查) |
| 测试 | pytest + pytest-asyncio |
## 二、架构总览
### 架构风格
**DDD + Clean Architecture + 微服务**
```
┌─────────────────────────────────────────────────────────────┐
│ 微服务拆分 │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ │ data-service│ │chart-service│ │template-svc │ │export-service│
│ │ :8001 │ │ :8002 │ │ :8003 │ │ :8004 │
│ └─────────────┘ └─────────────┘ └──────────────┘ └──────────────┘
│ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ PostgreSQL :5432 ││
│ │ data_db │ chart_db │ template_db │ export_db ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
```
### 服务职责
| 服务 | 端口 | 职责 | 数据库 |
|------|------|------|--------|
| **data-service** | 8001 | 文件上传、解析、数据集 CRUD、字段推断 | data_db |
| **chart-service** | 8002 | 图表实例 CRUD、字段绑定、样式配置、ECharts option 生成 | chart_db |
| **template-service** | 8003 | 模板 CRUD、导入导出、内置模板 | template_db |
| **export-service** | 8004 | 多格式导出PNG/PDF/PPT/Excel/HTML | export_db (任务队列) |
### 服务间依赖关系
```
data-service ← 独立,不依赖其他服务
chart-service ← 依赖 data-service获取数据集结构和数据
template-service ← 独立,不依赖其他服务
export-service ← 依赖 data-service获取数据
← 依赖 chart-service获取图表配置和 ECharts option
依赖方向为单向树形,无循环依赖
服务间通信方式HTTP 内部调用Docker 内网)
```
## 三、每个微服务内部的 Clean Architecture 分层
```
┌─────────────────────────────────────────────┐
│ Infrastructure基础设施层
│ FastAPI Routes · SQLAlchemy · 外部服务客户端 │
├─────────────────────────────────────────────┤
│ Interface Adapters适配层
│ Controllers · Repositories Impl · Presenters│
├─────────────────────────────────────────────┤
│ Application应用层
│ Use Cases · Input/Output Ports · DTOs │
├─────────────────────────────────────────────┤
│ Domain领域层
│ Entities · Value Objects · Domain Services │
└─────────────────────────────────────────────┘
依赖方向:外层 → 内层(绝对不可反向)
```
## 四、Monorepo 目录结构
```
backend/
├── docker-compose.yml # 一键编排所有服务 + 数据库
├── pyproject.toml # Monorepo 根配置
├── shared/ # 跨服务共享代码
│ ├── __init__.py
│ ├── base_entity.py # 实体基类id, created_at, updated_at
│ ├── base_repository.py # 仓储接口基类
│ ├── base_use_case.py # 用例接口基类
│ ├── exceptions.py # 公共异常定义
│ ├── types.py # 公共类型FieldType, ChartType 等枚举)
│ └── http_client.py # 服务间 HTTP 调用客户端
├── services/
│ ├── data-service/ # ========== 数据服务 ==========
│ │ ├── Dockerfile
│ │ ├── pyproject.toml
│ │ ├── alembic/ # 数据库迁移
│ │ │ ├── alembic.ini
│ │ │ └── versions/
│ │ │
│ │ └── src/
│ │ ├── domain/ # 领域层
│ │ │ ├── entities/
│ │ │ │ ├── dataset.py # DataSet 聚合根
│ │ │ │ ├── column.py # Column 实体
│ │ │ │ └── data_row.py # DataRow 值对象
│ │ │ ├── value_objects/
│ │ │ │ ├── field_type.py # FieldType 枚举
│ │ │ │ ├── file_format.py # FileFormat 枚举
│ │ │ │ └── data_structure.py # DataStructure 枚举
│ │ │ ├── services/
│ │ │ │ ├── field_type_inference.py # 字段类型推断规则
│ │ │ │ └── data_structure_inference.py # 数据结构推断规则
│ │ │ └── repositories/
│ │ │ └── dataset_repository.py # 仓储接口(抽象类)
│ │ │
│ │ ├── application/ # 应用层
│ │ │ ├── ports/
│ │ │ │ ├── input/
│ │ │ │ │ ├── import_data.py # 导入数据用例接口
│ │ │ │ │ ├── get_dataset.py # 获取数据集用例接口
│ │ │ │ │ └── list_datasets.py # 列表数据集用例接口
│ │ │ │ └── output/
│ │ │ │ └── file_parser.py # 文件解析端口接口
│ │ │ ├── usecases/
│ │ │ │ ├── import_data_usecase.py # 导入数据用例实现
│ │ │ │ ├── get_dataset_usecase.py # 获取数据集
│ │ │ │ ├── list_datasets_usecase.py # 列表数据集
│ │ │ │ └── delete_dataset_usecase.py # 删除数据集
│ │ │ └── dto/
│ │ │ ├── import_result.py
│ │ │ └── dataset_response.py
│ │ │
│ │ ├── adapters/ # 适配层
│ │ │ ├── persistence/
│ │ │ │ ├── models.py # SQLAlchemy ORM 模型
│ │ │ │ ├── dataset_repository_impl.py # 仓储实现
│ │ │ │ └── database.py # 数据库连接配置
│ │ │ ├── parsers/
│ │ │ │ ├── xlsx_parser.py # Excel 解析器
│ │ │ │ ├── csv_parser.py # CSV 解析器
│ │ │ │ ├── json_parser.py # JSON 解析器
│ │ │ │ └── parser_factory.py # 解析器工厂
│ │ │ └── presenters/
│ │ │ └── dataset_presenter.py # Entity → API Response
│ │ │
│ │ └── infrastructure/ # 基础设施层
│ │ ├── api/
│ │ │ ├── app.py # FastAPI 应用实例
│ │ │ ├── routes.py # 路由定义
│ │ │ └── dependencies.py # 依赖注入
│ │ ├── config.py # 配置(环境变量)
│ │ └── main.py # 入口 uvicorn
│ │
│ ├── chart-service/ # ========== 图表服务 ==========
│ │ ├── Dockerfile
│ │ ├── pyproject.toml
│ │ ├── alembic/
│ │ │
│ │ └── src/
│ │ ├── domain/
│ │ │ ├── entities/
│ │ │ │ ├── chart_instance.py # ChartInstance 聚合根
│ │ │ │ ├── field_binding.py # FieldBinding 实体
│ │ │ │ └── style_config.py # StyleConfig 值对象
│ │ │ ├── value_objects/
│ │ │ │ └── chart_type.py # ChartType 枚举 + 元数据
│ │ │ ├── services/
│ │ │ │ ├── chart_recommendation.py # 图表推荐规则
│ │ │ │ ├── binding_validation.py # 绑定合法性校验
│ │ │ │ └── option_builder.py # ECharts option 生成(领域规则)
│ │ │ └── repositories/
│ │ │ └── chart_repository.py
│ │ │
│ │ ├── application/
│ │ │ ├── ports/
│ │ │ │ ├── input/
│ │ │ │ │ ├── create_chart.py
│ │ │ │ │ ├── update_chart.py
│ │ │ │ │ ├── get_chart_option.py # 获取 ECharts option
│ │ │ │ │ └── recommend_charts.py # 推荐图表
│ │ │ │ └── output/
│ │ │ │ └── data_service_client.py # 调用 data-service 的端口
│ │ │ ├── usecases/
│ │ │ │ ├── create_chart_usecase.py
│ │ │ │ ├── update_chart_usecase.py
│ │ │ │ ├── get_chart_option_usecase.py
│ │ │ │ └── recommend_charts_usecase.py
│ │ │ └── dto/
│ │ │ ├── chart_response.py
│ │ │ └── echarts_option.py
│ │ │
│ │ ├── adapters/
│ │ │ ├── persistence/
│ │ │ │ ├── models.py
│ │ │ │ ├── chart_repository_impl.py
│ │ │ │ └── database.py
│ │ │ ├── clients/
│ │ │ │ └── data_service_client_impl.py # HTTP 调用 data-service
│ │ │ ├── option_builders/ # ECharts option 适配
│ │ │ │ ├── bar_builder.py
│ │ │ │ ├── line_builder.py
│ │ │ │ ├── pie_builder.py
│ │ │ │ ├── scatter_builder.py
│ │ │ │ ├── radar_builder.py
│ │ │ │ ├── heatmap_builder.py
│ │ │ │ ├── map_builder.py
│ │ │ │ ├── wordcloud_builder.py
│ │ │ │ ├── combo_builder.py
│ │ │ │ └── builder_factory.py
│ │ │ └── presenters/
│ │ │ └── chart_presenter.py
│ │ │
│ │ └── infrastructure/
│ │ ├── api/
│ │ │ ├── app.py
│ │ │ ├── routes.py
│ │ │ └── dependencies.py
│ │ ├── config.py
│ │ └── main.py
│ │
│ ├── template-service/ # ========== 模板服务 ==========
│ │ ├── Dockerfile
│ │ ├── pyproject.toml
│ │ ├── alembic/
│ │ │
│ │ └── src/
│ │ ├── domain/
│ │ │ ├── entities/
│ │ │ │ └── template.py # Template 聚合根
│ │ │ ├── value_objects/
│ │ │ │ └── template_type.py # 内置/自定义
│ │ │ ├── services/
│ │ │ │ └── template_validation.py # 模板合法性校验
│ │ │ └── repositories/
│ │ │ └── template_repository.py
│ │ │
│ │ ├── application/
│ │ │ ├── ports/
│ │ │ │ ├── input/
│ │ │ │ │ ├── save_template.py
│ │ │ │ │ ├── load_template.py
│ │ │ │ │ ├── list_templates.py
│ │ │ │ │ └── import_export_template.py
│ │ │ │ └── output/
│ │ │ │ └── template_storage.py # 持久化端口
│ │ │ ├── usecases/
│ │ │ │ ├── save_template_usecase.py
│ │ │ │ ├── load_template_usecase.py
│ │ │ │ ├── list_templates_usecase.py
│ │ │ │ ├── delete_template_usecase.py
│ │ │ │ └── import_export_usecase.py
│ │ │ └── dto/
│ │ │ └── template_response.py
│ │ │
│ │ ├── adapters/
│ │ │ ├── persistence/
│ │ │ │ ├── models.py
│ │ │ │ ├── template_repository_impl.py
│ │ │ │ └── database.py
│ │ │ └── presenters/
│ │ │ └── template_presenter.py
│ │ │
│ │ └── infrastructure/
│ │ ├── api/
│ │ │ ├── app.py
│ │ │ ├── routes.py
│ │ │ └── dependencies.py
│ │ ├── config.py
│ │ ├── seed/ # 内置模板种子数据
│ │ │ └── builtin_templates.json
│ │ └── main.py
│ │
│ └── export-service/ # ========== 导出服务 ==========
│ ├── Dockerfile
│ ├── pyproject.toml
│ ├── alembic/
│ │
│ └── src/
│ ├── domain/
│ │ ├── entities/
│ │ │ └── export_task.py # ExportTask 聚合根
│ │ ├── value_objects/
│ │ │ ├── export_format.py # PNG/PDF/PPT/Excel/HTML
│ │ │ └── export_status.py # pending/processing/done/failed
│ │ ├── services/
│ │ │ └── export_strategy.py # 导出策略选择规则
│ │ └── repositories/
│ │ └── export_task_repository.py
│ │
│ ├── application/
│ │ ├── ports/
│ │ │ ├── input/
│ │ │ │ ├── create_export.py
│ │ │ │ └── get_export_status.py
│ │ │ └── output/
│ │ │ ├── data_service_client.py
│ │ │ ├── chart_service_client.py
│ │ │ ├── image_renderer.py # 图片渲染端口
│ │ │ ├── pdf_generator.py # PDF 生成端口
│ │ │ ├── ppt_generator.py # PPT 生成端口
│ │ │ ├── excel_generator.py # Excel 生成端口
│ │ │ └── html_generator.py # HTML 生成端口
│ │ ├── usecases/
│ │ │ ├── create_export_usecase.py
│ │ │ └── get_export_status_usecase.py
│ │ └── dto/
│ │ ├── export_request.py
│ │ └── export_response.py
│ │
│ ├── adapters/
│ │ ├── persistence/
│ │ │ ├── models.py
│ │ │ ├── export_task_repository_impl.py
│ │ │ └── database.py
│ │ ├── clients/
│ │ │ ├── data_service_client_impl.py
│ │ │ └── chart_service_client_impl.py
│ │ ├── generators/
│ │ │ ├── playwright_image_renderer.py # Playwright 截图
│ │ │ ├── weasyprint_pdf_generator.py # PDF
│ │ │ ├── pptx_generator.py # PPT
│ │ │ ├── openpyxl_excel_generator.py # Excel
│ │ │ └── jinja2_html_generator.py # HTML
│ │ └── presenters/
│ │ └── export_presenter.py
│ │
│ └── infrastructure/
│ ├── api/
│ │ ├── app.py
│ │ ├── routes.py
│ │ └── dependencies.py
│ ├── config.py
│ └── main.py
└── tests/ # 测试(镜像 services 结构)
├── data-service/
│ ├── unit/
│ │ ├── domain/
│ │ └── application/
│ └── integration/
├── chart-service/
│ ├── unit/
│ └── integration/
├── template-service/
│ ├── unit/
│ └── integration/
└── export-service/
├── unit/
└── integration/
```
## 五、DDD 领域模型
### 限界上下文映射
```
┌─────────────────┐ ┌─────────────────┐
│ Data Context │ │ Chart Context │
│ │ │ │
│ DataSet (聚合根) │────→│ ChartInstance │
│ Column │ 引用 │ (聚合根) │
│ DataRow │ ID │ FieldBinding │
│ │ │ StyleConfig │
└─────────────────┘ └─────────────────┘
┌─────────────────┐ │ 引用 ID
│Template Context │ │
│ │ │
│ Template (聚合根) │←──┘
│ │
└─────────────────┘
┌─────────────────┐
│ Export Context │
│ │
│ ExportTask (聚合根)│
│ │
└─────────────────┘
引用 DataSet ID
引用 ChartInstance ID
```
### 聚合根设计
```python
# data-service: DataSet 聚合根
class DataSet:
id: UUID
file_name: str
sheet_name: Optional[str]
columns: List[Column] # 聚合内实体
row_count: int
data_structure: DataStructure # 推断出的数据结构类型
created_at: datetime
updated_at: datetime
def infer_field_types(self) -> None: ...
def infer_data_structure(self) -> None: ...
def get_column_by_name(self, name: str) -> Column: ...
def validate(self) -> None: ...
# chart-service: ChartInstance 聚合根
class ChartInstance:
id: UUID
dataset_id: UUID # 引用,非嵌套
chart_type: ChartType
bindings: List[FieldBinding] # 聚合内实体
style: StyleConfig # 聚合内值对象
filters: List[FilterRule]
sort: Optional[SortConfig]
top_n: Optional[int]
created_at: datetime
updated_at: datetime
def update_bindings(self, bindings: List[FieldBinding]) -> None: ...
def update_style(self, style: StyleConfig) -> None: ...
def validate_bindings(self) -> None: ...
# template-service: Template 聚合根
class Template:
id: UUID
name: str
description: str
template_type: TemplateType # builtin / custom
chart_configs: List[dict] # 图表配置 JSON
layout: List[dict] # 布局配置 JSON
theme: str
created_at: datetime
updated_at: datetime
def validate(self) -> None: ...
def to_export_json(self) -> dict: ...
@classmethod
def from_import_json(cls, data: dict) -> 'Template': ...
# export-service: ExportTask 聚合根
class ExportTask:
id: UUID
format: ExportFormat
chart_ids: List[UUID]
dataset_id: UUID
status: ExportStatus # pending → processing → done / failed
file_path: Optional[str]
error_message: Optional[str]
created_at: datetime
completed_at: Optional[datetime]
def start_processing(self) -> None: ...
def complete(self, file_path: str) -> None: ...
def fail(self, error: str) -> None: ...
```
## 六、Clean Architecture 依赖方向
以 data-service 为例:
```
infrastructure/ adapters/ application/ domain/
(FastAPI, SQLAlchemy) (实现层) (用例层) (领域层)
routes.py ──→ dependencies.py ──→ usecases/ ──→ entities/
│ │ │
│ │ value_objects/
│ │ │
│ │ services/
│ │ │
│ ▼ │
│ ports/input │
│ ports/output ←────┘
│ ▲
│ │(实现接口)
├──→ persistence/ │
├──→ parsers/ │
└──→ presenters/
import 方向全部从外向内
ports/output 定义接口在 application 层,实现在 adapters 层(依赖反转)
```
## 七、依赖注入
```python
# infrastructure/api/dependencies.py
# 组装所有端口实现,注入到 Use Case
from adapters.persistence.database import get_session
from adapters.persistence.dataset_repository_impl import DataSetRepositoryImpl
from adapters.parsers.parser_factory import ParserFactory
from application.usecases.import_data_usecase import ImportDataUseCase
async def get_import_data_usecase(
session: AsyncSession = Depends(get_session),
) -> ImportDataUseCase:
repository = DataSetRepositoryImpl(session)
file_parser = ParserFactory()
return ImportDataUseCase(
repository=repository,
file_parser=file_parser,
)
# infrastructure/api/routes.py
from fastapi import APIRouter, Depends, UploadFile
from infrastructure.api.dependencies import get_import_data_usecase
router = APIRouter()
@router.post("/datasets/import")
async def import_data(
file: UploadFile,
usecase: ImportDataUseCase = Depends(get_import_data_usecase),
):
result = await usecase.execute(file)
return DataSetPresenter.to_response(result)
```
## 八、API 设计
### data-service (:8001)
```
POST /api/v1/datasets/import 上传并解析文件
GET /api/v1/datasets 列出所有数据集
GET /api/v1/datasets/{id} 获取数据集详情(含列信息)
GET /api/v1/datasets/{id}/rows 获取数据行(分页)
PATCH /api/v1/datasets/{id}/rows/{row} 修改单元格
DELETE /api/v1/datasets/{id} 删除数据集
GET /api/v1/datasets/{id}/structure 获取推断的数据结构
```
### chart-service (:8002)
```
POST /api/v1/charts 创建图表实例
GET /api/v1/charts 列出所有图表
GET /api/v1/charts/{id} 获取图表配置
PUT /api/v1/charts/{id} 更新图表配置(绑定+样式)
DELETE /api/v1/charts/{id} 删除图表
GET /api/v1/charts/{id}/option 获取 ECharts option JSON
POST /api/v1/charts/recommend 根据数据集推荐图表类型
```
### template-service (:8003)
```
POST /api/v1/templates 保存模板
GET /api/v1/templates 列出所有模板(含内置)
GET /api/v1/templates/{id} 获取模板详情
PUT /api/v1/templates/{id} 更新模板
DELETE /api/v1/templates/{id} 删除模板
POST /api/v1/templates/import 导入模板 JSON
GET /api/v1/templates/{id}/export 导出模板 JSON
```
### export-service (:8004)
```
POST /api/v1/exports 创建导出任务
GET /api/v1/exports/{id} 查询导出状态
GET /api/v1/exports/{id}/download 下载导出文件
```
## 九、服务间通信
```python
# chart-service 调用 data-service 获取数据集
# application/ports/output/data_service_client.py端口接口
from abc import ABC, abstractmethod
class IDataServiceClient(ABC):
@abstractmethod
async def get_dataset(self, dataset_id: UUID) -> DataSetDTO: ...
@abstractmethod
async def get_rows(self, dataset_id: UUID, limit: int, offset: int) -> List[dict]: ...
# adapters/clients/data_service_client_impl.py适配实现
import httpx
class DataServiceClientImpl(IDataServiceClient):
def __init__(self, base_url: str = "http://data-service:8000"):
self.base_url = base_url
async def get_dataset(self, dataset_id: UUID) -> DataSetDTO:
async with httpx.AsyncClient() as client:
resp = await client.get(f"{self.base_url}/api/v1/datasets/{dataset_id}")
resp.raise_for_status()
return DataSetDTO(**resp.json())
async def get_rows(self, dataset_id: UUID, limit: int, offset: int) -> List[dict]:
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{self.base_url}/api/v1/datasets/{dataset_id}/rows",
params={"limit": limit, "offset": offset},
)
resp.raise_for_status()
return resp.json()["rows"]
```
## 十、数据库设计
### data_db
```sql
CREATE TABLE datasets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_name VARCHAR(255) NOT NULL,
sheet_name VARCHAR(255),
row_count INTEGER NOT NULL DEFAULT 0,
data_structure VARCHAR(50), -- single_dim / dual_dim / time_series / geo ...
raw_data JSONB NOT NULL, -- 原始行数据
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE columns (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dataset_id UUID NOT NULL REFERENCES datasets(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
field_type VARCHAR(50) NOT NULL, -- number / text / date / percentage / geo
sample_values JSONB,
ordinal INTEGER NOT NULL -- 列顺序
);
```
### chart_db
```sql
CREATE TABLE chart_instances (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dataset_id UUID NOT NULL, -- 引用 data_db.datasets.id跨库引用不加外键
chart_type VARCHAR(50) NOT NULL,
bindings JSONB NOT NULL, -- FieldBinding[]
style JSONB NOT NULL, -- StyleConfig
filters JSONB DEFAULT '[]',
sort_config JSONB,
top_n INTEGER,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### template_db
```sql
CREATE TABLE templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
template_type VARCHAR(20) NOT NULL DEFAULT 'custom', -- builtin / custom
chart_configs JSONB NOT NULL, -- 图表配置数组
layout JSONB NOT NULL, -- 布局配置数组
theme VARCHAR(50) NOT NULL DEFAULT 'light',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### export_db
```sql
CREATE TABLE export_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
format VARCHAR(20) NOT NULL, -- png / pdf / ppt / excel / html
chart_ids UUID[] NOT NULL,
dataset_id UUID NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending / processing / done / failed
file_path VARCHAR(500),
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
```
## 十一、Docker Compose 编排
```yaml
# docker-compose.yml
version: "3.9"
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: dataviz
POSTGRES_PASSWORD: dataviz_local
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
- ./init-databases.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dataviz"]
interval: 5s
retries: 5
data-service:
build: ./services/data-service
ports:
- "8001:8000"
environment:
DATABASE_URL: postgresql+asyncpg://dataviz:dataviz_local@postgres:5432/data_db
depends_on:
postgres:
condition: service_healthy
chart-service:
build: ./services/chart-service
ports:
- "8002:8000"
environment:
DATABASE_URL: postgresql+asyncpg://dataviz:dataviz_local@postgres:5432/chart_db
DATA_SERVICE_URL: http://data-service:8000
depends_on:
postgres:
condition: service_healthy
data-service:
condition: service_started
template-service:
build: ./services/template-service
ports:
- "8003:8000"
environment:
DATABASE_URL: postgresql+asyncpg://dataviz:dataviz_local@postgres:5432/template_db
depends_on:
postgres:
condition: service_healthy
export-service:
build: ./services/export-service
ports:
- "8004:8000"
environment:
DATABASE_URL: postgresql+asyncpg://dataviz:dataviz_local@postgres:5432/export_db
DATA_SERVICE_URL: http://data-service:8000
CHART_SERVICE_URL: http://chart-service:8000
depends_on:
postgres:
condition: service_healthy
data-service:
condition: service_started
chart-service:
condition: service_started
volumes:
pgdata:
```
```sql
-- init-databases.sql
CREATE DATABASE data_db;
CREATE DATABASE chart_db;
CREATE DATABASE template_db;
CREATE DATABASE export_db;
```
## 十二、本地开发启动
```bash
# 一键启动所有服务
docker-compose up --build
# 服务地址
# data-service → http://localhost:8001/docs
# chart-service → http://localhost:8002/docs
# template-service → http://localhost:8003/docs
# export-service → http://localhost:8004/docs
# 单独重启某个服务(开发时)
docker-compose restart chart-service
# 查看日志
docker-compose logs -f export-service
```
## 十三、前端对接配置
```typescript
// 前端服务地址枚举
enum ServiceURL {
Data = 'http://localhost:8001',
Chart = 'http://localhost:8002',
Template = 'http://localhost:8003',
Export = 'http://localhost:8004',
}
// 示例调用
const response = await fetch(`${ServiceURL.Data}/api/v1/datasets/import`, {
method: 'POST',
body: formData,
});
```