From 9f6ecf33f9756689d5757dfa753b92d529ae4b50 Mon Sep 17 00:00:00 2001 From: Bot Date: Sun, 30 Aug 2026 23:51:12 +0800 Subject: [PATCH] feat: scaffold modular monolith backend architecture --- ARCHITECTURE.md | 112 ++++++++++++++++++++++ Dockerfile | 21 ++++ app/__init__.py | 0 app/api/routes/__init__.py | 0 app/config.py | 44 +++++++++ app/domains/ledger/__init__.py | 0 app/domains/market/__init__.py | 0 app/domains/settlement/__init__.py | 0 app/domains/trading/__init__.py | 0 app/domains/user/__init__.py | 0 app/domains/wallet/__init__.py | 0 app/infrastructure/blockchain/__init__.py | 0 app/infrastructure/database/__init__.py | 0 app/infrastructure/external/__init__.py | 0 app/infrastructure/redis/__init__.py | 0 app/main.py | 39 ++++++++ app/models/__init__.py | 0 app/schemas/__init__.py | 0 app/ws/__init__.py | 0 docker-compose.yml | 42 ++++++++ requirements.txt | 15 +++ 21 files changed, 273 insertions(+) create mode 100644 ARCHITECTURE.md create mode 100644 Dockerfile create mode 100644 app/__init__.py create mode 100644 app/api/routes/__init__.py create mode 100644 app/config.py create mode 100644 app/domains/ledger/__init__.py create mode 100644 app/domains/market/__init__.py create mode 100644 app/domains/settlement/__init__.py create mode 100644 app/domains/trading/__init__.py create mode 100644 app/domains/user/__init__.py create mode 100644 app/domains/wallet/__init__.py create mode 100644 app/infrastructure/blockchain/__init__.py create mode 100644 app/infrastructure/database/__init__.py create mode 100644 app/infrastructure/external/__init__.py create mode 100644 app/infrastructure/redis/__init__.py create mode 100644 app/main.py create mode 100644 app/models/__init__.py create mode 100644 app/schemas/__init__.py create mode 100644 app/ws/__init__.py create mode 100644 docker-compose.yml create mode 100644 requirements.txt diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..e260434 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,112 @@ +# WTFX Backend Architecture Rules (后端架构铁律) + +> **核心定位**:WTFX 采用 **Python + FastAPI + PostgreSQL + Redis** 的**模块化单体 (Modular Monolith)** 架构。 +> **适用对象**:所有开发者与 AI Coding Agent。在编写或修改任何后端代码之前,**必须严格遵守本规范**。 + +--- + +## 1. 架构核心铁律 (Non-Negotiable Rules) + +1. **【分层单向依赖】**: + - 依赖调用链严格为:`API Route -> Domain Service -> Repository -> Database/Infrastructure`。 + - **严禁反向或跨层调用**(如 Route 直连 DB,或 Repo 调用 Service)。 +2. **【禁止 Route 直连数据库】**: + - API 路由层(`app/api/`)只允许做:入参校验 (Pydantic Schema)、鉴权依赖注入、调用 Domain Service、返回标准响应。 + - **严禁在 Route 中执行 SQL 或 ORM 查询**。 +3. **【Domain 纯净性】**: + - 业务领域层(`app/domains/`)代表纯粹的业务大脑,**严禁导入 FastAPI 或任何 HTTP/WebSocket 相关的 Web 框架依赖**。 +4. **【资金与状态变更强制走 Ledger】**: + - 严禁直接执行 `user.balance += amount` 等无据操作。 + - 所有账户资金变动(充值、下注扣款、保证金冻结/解冻、手续费、获胜兑付)**必须通过 LedgerService 生成不可变的复式记账流水 (`LedgerEntry`)**。 +5. **【外部第三方服务强制使用 Adapter 隔离】**: + - 链上 RPC、Hyperliquid、价格预言机等外部系统交互必须统一封装在 `app/infrastructure/` 适配器中。 + - Domain 业务层只依赖抽象接口(如 `TradingProvider` / `ChainClient`),不感知底层具体 API 细节。 +6. **【事件总线驱动,解耦 WebSocket】**: + - 业务状态变更(如订单成交、市场毕业)只向 `EventBus` 发送领域事件。 + - WebSocket 模块(`app/ws/`)独立订阅事件并广播给在线客户端,业务逻辑层严禁直接依赖 WS 连接对象。 +7. **【Model 与 Schema 严格分离】**: + - `models/` 映射数据库实体,`schemas/` 定义 API 输入输出结构体。 + - **严禁将数据库 Model 直接作为 API Response 返回**,防止敏感字段(密码哈希、内部风控分等)意外泄露。 + +--- + +## 2. 目录职责分工 + +``` +wtf-backend/ +├── app/ +│ ├── main.py # 应用入口与生命周期管理 (Lifespan) +│ ├── config.py # Pydantic Settings 环境变量配置 +│ ├── logging.py # 结构化日志配置 +│ │ +│ ├── api/ # HTTP 接口接入层 (只做请求解析与转发) +│ │ ├── dependencies.py # 共享依赖注入 (DB Session, 当前用户, Auth) +│ │ └── routes/ # 各业务模块的路由入口 +│ │ ├── auth.py # Web3 SIWE 钱包登录与 JWT +│ │ ├── markets.py # 预测市场查询与建盘 +│ │ ├── orders.py # 交易下单与撤单 +│ │ ├── positions.py # 用户持仓查询 +│ │ └── wallet.py # 充值、提现与流水 +│ │ +│ ├── domains/ # 业务领域大脑 (纯业务逻辑,无 Web 依赖) +│ │ ├── market/ # 市场状态机与联合曲线规则 +│ │ ├── trading/ # 交易撮合与风控检查 +│ │ ├── ledger/ # 【核心】不可变流水账本 +│ │ ├── wallet/ # 钱包余额状态管理 +│ │ ├── settlement/ # 预测到期结算与兑付 +│ │ └── user/ # 用户信息与邀请返佣 +│ │ +│ ├── infrastructure/ # 外部世界适配层 (数据库、缓存、链上、三方 API) +│ │ ├── database/ # PostgreSQL 连接池与异步 Session 工厂 +│ │ ├── redis/ # Redis 缓存与 Streams 事件总线 +│ │ ├── blockchain/ # EVM RPC 监听与合约调用客户端 +│ │ └── external/ # Hyperliquid / CLOB 等外部交易适配器 +│ │ +│ ├── models/ # SQLAlchemy 2.0 异步数据库实体 +│ │ ├── market.py +│ │ ├── order.py +│ │ ├── position.py +│ │ ├── ledger.py +│ │ └── user.py +│ │ +│ ├── schemas/ # Pydantic v2 请求与响应数据传输对象 (DTO) +│ │ ├── market.py +│ │ ├── order.py +│ │ ├── ledger.py +│ │ └── common.py +│ │ +│ └── ws/ # WebSocket 实时网关 (行情推流与用户私有信道) +│ ├── manager.py # 连接池与订阅频道管理 +│ └── handlers.py # WS 消息解析与心跳 +│ +├── migrations/ # Alembic 数据库迁移脚本 +├── tests/ # 单元测试与集成测试 +├── Dockerfile # 生产镜像构建文件 +├── docker-compose.yml # 本地开发与服务器运行编排 +└── requirements.txt # 锁定依赖项 +``` + +--- + +## 3. 核心业务流程标准示例 (How to Write Code) + +### 正确的下单流程示例 (Trading Flow): +``` +[Client] ---> POST /api/v1/orders + │ + ▼ + [orders.py (Route)] + │ (1. 解析 CreateOrderRequest Schema) + │ (2. 依赖注入 TradingService) + ▼ + [TradingService (Domain)] + │ (1. 风险检查 RiskCheck) + │ (2. 冻结资金: 调用 LedgerService.freeze_balance) + │ (3. 创建订单: 调用 OrderRepository.create) + │ (4. 抛出事件: EventBus.publish("OrderCreated")) + ▼ + [PostgreSQL] (Commit Transaction) + │ + ▼ + [WS Gateway] (异步监听到 OrderCreated -> 广播行情) +``` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3f1416b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM python:3.11-slim + +WORKDIR /app + +# 安装基础编译依赖 +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + libpq-dev \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# 安装 Python 依赖 +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# 复制代码 +COPY . . + +EXPOSE 8080 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080", "--workers", "4"] diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/routes/__init__.py b/app/api/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..0cb44c3 --- /dev/null +++ b/app/config.py @@ -0,0 +1,44 @@ +from pydantic_settings import BaseSettings +from typing import List + +class Settings(BaseSettings): + PROJECT_NAME: str = "WTFX Backend API" + VERSION: str = "2.0.0" + DEBUG: bool = False + + # 环境:development, staging, production + ENVIRONMENT: str = "development" + + # 数据库配置 (PostgreSQL) + DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/wtfx" + + # Redis 配置 + REDIS_URL: str = "redis://localhost:6379/0" + + # JWT 鉴权 + JWT_SECRET: str = "wtfx_secret_jwt_key_change_in_production" + JWT_ALGORITHM: str = "HS256" + ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 days + + # 跨域设置 + CORS_ORIGINS: List[str] = [ + "http://localhost:3000", + "http://localhost:3001", + "https://wtfx.app", + "https://test.wtfx.app", + "https://admin.wtfx.app", + "https://admin.test.wtfx.app" + ] + + # 链上 RPC 配置 (Robinhood Chain) + ROBINHOOD_TESTNET_RPC: str = "https://rpc.testnet.chain.robinhood.com" + ROBINHOOD_MAINNET_RPC: str = "https://rpc.chain.robinhood.com" + + # 控制器合约地址 (由部署脚本生成) + CONTROLLER_ADDRESS: str = "0x0000000000000000000000000000000000000000" + + class Config: + env_file = ".env" + case_sensitive = True + +settings = Settings() diff --git a/app/domains/ledger/__init__.py b/app/domains/ledger/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/domains/market/__init__.py b/app/domains/market/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/domains/settlement/__init__.py b/app/domains/settlement/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/domains/trading/__init__.py b/app/domains/trading/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/domains/user/__init__.py b/app/domains/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/domains/wallet/__init__.py b/app/domains/wallet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/infrastructure/blockchain/__init__.py b/app/infrastructure/blockchain/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/infrastructure/database/__init__.py b/app/infrastructure/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/infrastructure/external/__init__.py b/app/infrastructure/external/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/infrastructure/redis/__init__.py b/app/infrastructure/redis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..9a0bced --- /dev/null +++ b/app/main.py @@ -0,0 +1,39 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from contextlib import asynccontextmanager +from app.config import settings + +@asynccontextmanager +async def lifespan(app: FastAPI): + # 启动时:初始化 DB 连接池、Redis 事件总线与外部适配器 + print(f"Starting {settings.PROJECT_NAME} in [{settings.ENVIRONMENT}] mode...") + yield + # 关闭时:优雅断开连接 + print(f"Shutting down {settings.PROJECT_NAME}...") + +app = FastAPI( + title=settings.PROJECT_NAME, + version=settings.VERSION, + lifespan=lifespan, + docs_url="/docs" if settings.ENVIRONMENT != "production" else None, + redoc_url="/redoc" if settings.ENVIRONMENT != "production" else None +) + +# CORS 配置 +app.add_middleware( + CORSMiddleware, + allow_origins=settings.CORS_ORIGINS, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/health", tags=["System"]) +async def health_check(): + """生产探活与健康检查端点 (Blue-Green Probe)""" + return { + "status": "ok", + "service": "wtfx-backend", + "version": settings.VERSION, + "environment": settings.ENVIRONMENT + } diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/ws/__init__.py b/app/ws/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2877ae3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,42 @@ +version: '3.8' + +services: + backend: + build: . + container_name: wtfx-backend + restart: unless-stopped + ports: + - "8080:8080" + environment: + - ENVIRONMENT=production + - DATABASE_URL=postgresql+asyncpg://wtfx_user:wtfx_pass@postgres:5432/wtfx_db + - REDIS_URL=redis://redis:6379/0 + depends_on: + - postgres + - redis + + postgres: + image: postgres:16-alpine + container_name: wtfx-postgres + restart: unless-stopped + environment: + POSTGRES_USER: wtfx_user + POSTGRES_PASSWORD: wtfx_pass + POSTGRES_DB: wtfx_db + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" + + redis: + image: redis:7-alpine + container_name: wtfx-redis + restart: unless-stopped + volumes: + - redisdata:/data + ports: + - "6379:6379" + +volumes: + pgdata: + redisdata: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..eaf04f5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,15 @@ +fastapi>=0.111.0 +uvicorn[standard]>=0.30.0 +pydantic>=2.7.0 +pydantic-settings>=2.3.0 +sqlalchemy[asyncio]>=2.0.30 +asyncpg>=0.29.0 +alembic>=1.13.1 +redis>=5.0.4 +web3>=6.19.0 +eth-account>=0.11.2 +pyjwt>=2.8.0 +python-multipart>=0.0.9 +httpx>=0.27.0 +pytest>=8.2.0 +pytest-asyncio>=0.23.7