33 KiB
33 KiB
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
聚合根设计
# 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 层(依赖反转)
七、依赖注入
# 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 下载导出文件
九、服务间通信
# 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
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
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
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
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 编排
# 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:
-- init-databases.sql
CREATE DATABASE data_db;
CREATE DATABASE chart_db;
CREATE DATABASE template_db;
CREATE DATABASE export_db;
十二、本地开发启动
# 一键启动所有服务
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
十三、前端对接配置
// 前端服务地址枚举
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,
});