# 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, }); ```