# Docker Web 版本部署设计 ## 1. 架构概述 ### 1.1 整体架构 ``` ┌─────────────────────────────────────────────────┐ │ NAS 系统 │ │ │ │ ┌────────────────────────────────────────────┐ │ │ │ Docker 容器 │ │ │ │ │ │ │ │ ┌───────────────────────────────────────┐ │ │ │ │ │ Nginx (反向代理) │ │ │ │ │ │ - HTTPS │ │ │ │ │ │ - 静态文件服务 │ │ │ │ │ └───────────────────────────────────────┘ │ │ │ │ ↑ │ │ │ │ ┌───────────────────────────────────────┐ │ │ │ │ │ FastAPI Web 服务 │ │ │ │ │ │ - REST API │ │ │ │ │ │ - WebSocket (实时行情) │ │ │ │ │ │ - 认证授权 │ │ │ │ │ └───────────────────────────────────────┘ │ │ │ │ ↑ │ │ │ │ ┌───────────────────────────────────────┐ │ │ │ │ │ VeighNa 核心引擎 │ │ │ │ │ │ - 交易接口 │ │ │ │ │ │ - 策略引擎 │ │ │ │ │ │ - 事件引擎 │ │ │ │ │ └───────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ SQLite │ │ 数据文件 │ │ 日志文件 │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ ↓ ↓ ↓ │ │ │ │ 持久化卷 (Docker Volume) │ │ │ └────────────────────────────────────────────┘ │ │ │ │ NAS 存储 (数据持久化) │ └─────────────────────────────────────────────────┘ ``` ### 1.2 组件说明 | 组件 | 技术 | 说明 | |-----|------|------| | Web 框架 | FastAPI | 高性能异步 Web 框架 | | 前端 | Vue.js 3 | 现代化响应式界面 | | 反向代理 | Nginx | HTTPS + 静态文件 | | 数据库 | SQLite | 轻量级,可选 PostgreSQL | | 实时通信 | WebSocket | 行情推送 | | 容器编排 | Docker Compose | 多容器管理 | ## 2. 技术方案 ### 2.1 目录结构 ``` sanguo_vnpy_v2/ ├── docker/ │ ├── Dockerfile # 主容器镜像 │ ├── docker-compose.yml # 编排配置 │ ├── nginx/ │ │ ├── Dockerfile # Nginx 镜像 │ │ ├── nginx.conf # Nginx 配置 │ │ └── ssl/ # SSL 证书 │ ├── entrypoint.sh # 启动脚本 │ └── requirements-docker.txt # Docker 依赖 ├── sanguo_web/ # Web 服务 │ ├── api/ # FastAPI 接口 │ │ ├── trading.py # 交易接口 │ │ ├── strategy.py # 策略接口 │ │ ├── data.py # 数据接口 │ │ └── auth.py # 认证接口 │ ├── websocket/ # WebSocket 处理 │ │ └── handler.py # 行情推送 │ ├── static/ # 前端静态文件 │ └── templates/ # HTML 模板 └── config/ ├── docker_config.json # Docker 配置 └── users.json # 用户配置 ``` ### 2.2 Dockerfile 设计 ```dockerfile # 多阶段构建 FROM python:3.11-slim as builder # 安装编译依赖 RUN apt-get update && apt-get install -y \ gcc g++ make \ && rm -rf /var/lib/apt/lists/* # 安装 TA-Lib ENV TA_LIBRARY_PATH=/usr/local/lib RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \ tar -xzf ta-lib-0.4.0-src.tar.gz && \ cd ta-lib && \ ./configure --prefix=/usr && \ make && make install # 复制依赖文件 COPY requirements-docker.txt . RUN pip install --no-cache-dir -r requirements-docker.txt # 运行阶段 FROM python:3.11-slim # 复译 TA-Lib COPY --from=builder /usr/lib/libta*.* /usr/local/lib/ COPY --from=builder /usr/include/ta-lib/ /usr/include/ # 设置工作目录 WORKDIR /app # 复制应用代码 COPY sanguo_trader/ ./sanguo_trader/ COPY sanguo_research/ ./sanguo_research/ COPY sanguo_data/ ./sanguo_data/ COPY sanguo_common/ ./sanguo_common/ COPY sanguo_web/ ./sanguo_web/ COPY vnpy_v4.4.0/vnpy/ ./vnpy/ COPY config/ ./config/ # 暴露端口 EXPOSE 8000 8080 # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1 # 启动命令 COPY docker/entrypoint.sh . RUN chmod +x entrypoint.sh ENTRYPOINT ["./entrypoint.sh"] ``` ### 2.3 Docker Compose 配置 ```yaml version: '3.8' services: vnpy: build: context: . dockerfile: docker/Dockerfile container_name: sanguo-vnpy restart: unless-stopped # 环境变量 environment: - PYTHONUNBUFFERED=1 - TZ=Asia/Shanghai - VNPY_LOG_LEVEL=INFO # 端口映射 ports: - "8000:8000" # FastAPI - "8080:8080" # WebSocket # 数据卷 volumes: - vnpy_data:/app/data - vnpy_logs:/app/logs - vnpy_config:/app/config - ./config:/app/config:ro # 网络 networks: - vnpy-network nginx: build: context: . dockerfile: docker/nginx/Dockerfile container_name: sanguo-nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./docker/nginx/ssl:/etc/nginx/ssl:ro - nginx_cache:/var/cache/nginx depends_on: - vnpy networks: - vnpy-network # 数据卷 volumes: vnpy_data: driver: local vnpy_logs: driver: local vnpy_config: driver: local nginx_cache: driver: local # 网络 networks: vnpy-network: driver: bridge ``` ### 2.4 Web API 设计 #### REST API 端点 ```python # sanguo_web/api/__init__.py from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware app = FastAPI( title="Sanguo VeighNa Web", description="量化交易平台 Web API", version="1.0.0" ) # CORS 配置 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 挂载静态文件 app.mount("/static", StaticFiles(directory="static"), name="static") # API 路由 from . import trading, strategy, data, auth app.include_router(trading.router, prefix="/api/trading", tags=["交易"]) app.include_router(strategy.router, prefix="/api/strategy", tags=["策略"]) app.include_router(data.router, prefix="/api/data", tags=["数据"]) app.include_router(auth.router, prefix="/api/auth", tags=["认证"]) ``` #### 交易接口示例 ```python # sanguo_web/api/trading.py from fastapi import APIRouter, HTTPException from pydantic import BaseModel router = APIRouter() class OrderRequest(BaseModel): symbol: str exchange: str direction: str offset: str price: float volume: int price_type: str = "LIMIT" @router.post("/orders") async def send_order(req: OrderRequest): """发送订单""" # 调用 VeighNa 引擎 order_id = engine.send_order(req) return {"status": "success", "order_id": order_id} @router.delete("/orders/{order_id}") async def cancel_order(order_id: str): """撤销订单""" engine.cancel_order(order_id) return {"status": "success"} @router.get("/orders") async def get_orders(): """查询订单""" orders = engine.get_all_orders() return {"orders": orders} @router.get("/positions") async def get_positions(): """查询持仓""" positions = engine.get_all_positions() return {"positions": positions} ``` ### 2.5 WebSocket 行情推送 ```python # sanguo_web/websocket/handler.py from fastapi import WebSocket from typing import Dict import json active_connections: Dict[str, WebSocket] = {} async def websocket_endpoint(websocket: WebSocket, client_id: str): await websocket.accept() active_connections[client_id] = websocket try: while True: # 接收客户端消息 data = await websocket.receive_text() msg = json.loads(data) if msg["type"] == "subscribe": # 订阅行情 subscribe_market_data(client_id, msg["symbol"]) elif msg["type"] == "unsubscribe": # 取消订阅 unsubscribe_market_data(client_id, msg["symbol"]) except Exception as e: print(f"Connection error: {e}") finally: del active_connections[client_id] async def broadcast_tick(tick_data): """广播行情数据""" tick_json = json.dumps({ "type": "tick", "data": tick_data }) for connection in active_connections.values(): await connection.send_text(tick_json) ``` ## 3. 部署方案 ### 3.1 NAS 部署步骤 #### Synology NAS ```bash # 1. 安装 Container Manager # 2. 导入镜像 # 3. 创建项目 # 4. 启动容器 ``` #### QNAP NAS ```bash # 1. 安装 Container Station # 2. 导入镜像 # 3. 创建容器 # 4. 启动 ``` #### 通用 Linux NAS ```bash # 1. 克隆项目 git clone http://192.168.2.154:3000/sanguo/sanguo_vnpy_v2 # 2. 启动服务 cd sanguo_vnpy_v2 docker-compose up -d # 3. 查看日志 docker-compose logs -f # 4. 停止服务 docker-compose down ``` ### 3.2 配置管理 ```yaml # config/docker_config.json { "server": { "host": "0.0.0.0", "port": 8000, "workers": 4 }, "database": { "type": "sqlite", "path": "/app/data/vnpy.db" }, "logging": { "level": "INFO", "path": "/app/logs" }, "security": { "enable_auth": true, "jwt_secret": "your-secret-key", "session_timeout": 3600 }, "trading": { "gateway_name": "CTP", "md_address": "", "td_address": "", "userid": "", "password": "", "appid": "", "authcode": "" } } ``` ### 3.3 数据持久化 ```bash # Docker 卷管理 docker volume create vnpy_data docker volume create vnpy_logs docker volume create vnpy_config # 查看卷 docker volume ls # 备份数据 docker run --rm -v vnpy_data:/data -v $(pwd):/backup \ alpine tar czf /backup/vnpy_data_backup.tar.gz /data ``` ## 4. 安全设计 ### 4.1 认证方案 ```python # sanguo_web/api/auth.py from fastapi import APIRouter, HTTPException, Depends from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials import jwt from datetime import datetime, timedelta router = APIRouter() security = HTTPBearer() SECRET_KEY = "your-secret-key" @router.post("/login") async def login(username: str, password: str): """用户登录""" # 验证用户名密码 if verify_user(username, password): token = create_access_token(username) return {"access_token": token} raise HTTPException(401, "Invalid credentials") @router.get("/verify") async def verify_token( credentials: HTTPAuthorizationCredentials = Depends(security) ): """验证 Token""" try: payload = jwt.decode( credentials.credentials, SECRET_KEY, algorithms=["HS256"] ) return {"valid": True, "user": payload["sub"]} except: raise HTTPException(401, "Invalid token") ``` ### 4.2 HTTPS 配置 ```nginx # docker/nginx/nginx.conf server { listen 443 ssl http2; server_name localhost; ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; location / { proxy_pass http://vnpy:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket 支持 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location /static/ { alias /app/static/; } } server { listen 80; server_name localhost; return 301 https://$server_name$request_uri; } ``` ## 5. 监控与日志 ### 5.1 健康检查 ```python @app.get("/health") async def health_check(): """健康检查接口""" return { "status": "healthy", "services": { "database": check_database(), "gateway": check_gateway(), "strategy": check_strategy() } } ``` ### 5.2 日志配置 ```python # sanguo_common/logger.py import logging from pathlib import Path def setup_logger(): """配置日志""" log_path = Path("/app/logs") log_path.mkdir(exist_ok=True) logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(log_path / "vnpy.log"), logging.StreamHandler() ] ) ``` ## 6. 扩展设计 ### 6.1 多容器部署 ```yaml # docker-compose.cluster.yml version: '3.8' services: vnpy-api: <<: *vnpy-service container_name: sanguo-vnpy-api-1 vnpy-api-2: <<: *vnpy-service container_name: sanguo-vnpy-api-2 nginx: # 负载均衡配置 # ... ``` ### 6.2 数据库升级 ```yaml # 使用 PostgreSQL services: postgres: image: postgres:15 container_name: sanguo-db environment: POSTGRES_DB: vnpy POSTGRES_USER: vnpy POSTGRES_PASSWORD: password volumes: - postgres_data:/var/lib/postgresql/data ``` ## 7. 实施计划 ### 阶段 1: 基础框架 - [ ] 创建 Dockerfile - [ ] 创建 docker-compose.yml - [ ] 实现基础 Web API - [ ] 实现认证授权 ### 阶段 2: 核心功能 - [ ] 实现交易接口 - [ ] 实现策略接口 - [ ] 实现 WebSocket 行情 - [ ] 实现数据查询接口 ### 阶段 3: 前端界面 - [ ] 实现登录页面 - [ ] 实现交易面板 - [ ] 实现行情显示 - [ ] 实现策略管理 ### 阶段 4: 部署优化 - [ ] Nginx 配置 - [ ] HTTPS 配置 - [ ] 数据持久化 - [ ] 监控日志 ### 阶段 5: 测试验证 - [ ] 功能测试 - [ ] 性能测试 - [ ] 部署测试 - [ ] 用户验收 ## 8. 构建经验总结 ### 8.1 已知问题与解决方案 #### 问题 1: polars CPU 兼容性 **错误现象**: ``` Missing required CPU features: avx, avx2, fma, bmi1, bmi2, lzcnt Container exit code: 132 ``` **原因分析**: - NAS CPU (Intel Celeron J4125) 不支持 AVX2 指令集 - polars 默认版本依赖 AVX 指令优化性能 - 在不支持的 CPU 上运行会直接崩溃 **解决方案**: 1. 修改 `requirements-docker.txt`: ```diff - polars>=1.26.0 + polars[rtcompat]>=1.26.0 ``` 2. 修改 `docker/entrypoint.sh`: ```bash #!/bin/bash set -e export POLARS_SKIP_CPU_CHECK=1 # 添加此行 # ... 其余内容 ``` #### 问题 2: 依赖冲突 **错误现象**: ``` ERROR: Cannot install -r requirements-docker.txt (line 59) ``` **原因分析**: - statsmodels 与其他包版本冲突 - 部分科学计算包版本不兼容 **解决方案**: - 使用经过测试的固定版本组合 - 参考成功的 `rebuild2.log` 中的版本 #### 问题 3: 网络超时 **错误现象**: ``` ERROR: Could not find a version that satisfies the requirement... Connection timeout ``` **解决方案**: 使用清华镜像加速: ```dockerfile ARG PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple ARG PIP_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn ``` ### 8.2 成功构建配置 #### 最终 requirements-docker.txt 关键配置 ```txt # CPU 兼容性配置 polars[rtcompat]>=1.26.0 # 科学计算包(已验证版本) numpy>=1.24.0,<2.0.0 pandas>=2.0.0 scipy>=1.10.0 statsmodels>=0.14.0 ``` #### entrypoint.sh 配置 ```bash #!/bin/bash set -e # CPU 兼容性跳过 export POLARS_SKIP_CPU_CHECK=1 # 服务启动 cd /app exec python -m uvicorn sanguo_web.main:app \ --host 0.0.0.0 \ --port 8000 \ --log-level info ``` ## 9. 分层构建设计 ### 9.1 设计目标 1. **分离依赖与应用**:固定的 Python 依赖包与变化的应用代码分离 2. **减少重建时间**:依赖包层只需构建一次,后续只重建应用层 3. **降低风险**:基础层冗余设计,包含所有可能需要的依赖 ### 9.2 分层架构 ``` ┌─────────────────────────────────────────────┐ │ 应用层 (Dockerfile.nas) │ │ - sanguo_*/ 源代码 │ │ - config/ 配置文件 │ │ - 只在代码变更时重建 │ └─────────────────────────────────────────────┘ ↓ FROM ┌─────────────────────────────────────────────┐ │ 基础层 (Dockerfile.base) │ │ - Python 3.11 │ │ - 所有依赖包 (~7GB) │ │ - TA-Lib │ │ - 只在依赖变更时重建 │ └─────────────────────────────────────────────┘ ↓ FROM ┌─────────────────────────────────────────────┐ │ python:3.11-slim │ └─────────────────────────────────────────────┘ ``` ### 9.3 Dockerfile.base ```dockerfile # sanguo_vnpy_v2/docker/Dockerfile.base FROM python:3.11-slim LABEL maintainer="sanguo" LABEL description="Sanguo VeighNa Base Image with all dependencies" # 设置工作目录 WORKDIR /build # 安装系统依赖 RUN apt-get update && apt-get install -y \ gcc g++ make wget \ build-essential \ libssl-dev libffi-dev \ && rm -rf /var/lib/apt/lists/* # 安装 TA-Lib ENV TA_LIBRARY_PATH=/usr/local/lib ENV TA_HEADER_PATH=/usr/include RUN wget -q http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \ tar -xzf ta-lib-0.4.0-src.tar.gz && \ cd ta-lib && \ ./configure --prefix=/usr && \ make && make install && \ cd .. && \ rm -rf ta-lib ta-lib-0.4.0-src.tar.gz # 使用清华镜像加速 ARG PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple ARG PIP_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn ENV PIP_INDEX_URL=${PIP_INDEX_URL} ENV PIP_TRUSTED_HOST=${PIP_TRUSTED_HOST} # CPU 兼容性配置 ENV POLARS_SKIP_CPU_CHECK=1 # 复制依赖文件 COPY requirements-docker.txt . # 安装所有 Python 依赖 RUN pip install --no-cache-dir --root-user-action=ignore -r requirements-docker.txt # 清理 RUN apt-get purge -y gcc g++ make wget && \ apt-get autoremove -y && \ rm -rf /var/lib/apt/lists/* /tmp/* # 设置最终工作目录 WORKDIR /app # 健康检查基础 RUN pip install --no-cache-dir uvicorn # 元数据标签 LABEL build_date="2025-07-02" LABEL python_version="3.11" LABEL description="Sanguo VeighNa Base - Ready for application layer" ``` ### 9.4 Dockerfile.nas (应用层) ```dockerfile # sanguo_vnpy_v2/docker/Dockerfile.nas FROM sanguo_vnpy:base LABEL maintainer="sanguo" LABEL description="Sanguo VeighNa Application Layer" # 复制应用代码 COPY sanguo_trader/ ./sanguo_trader/ COPY sanguo_research/ ./sanguo_research/ COPY sanguo_data/ ./sanguo_data/ COPY sanguo_common/ ./sanguo_common/ COPY sanguo_web/ ./sanguo_web/ COPY vnpy_v4.4.0/vnpy/ ./vnpy/ COPY config/ ./config/ # 复制启动脚本 COPY docker/entrypoint.sh . RUN chmod +x entrypoint.sh # 暴露端口 EXPOSE 8000 8080 # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1 # 启动 ENTRYPOINT ["./entrypoint.sh"] ``` ### 9.5 构建流程 #### 首次构建(全量) ```bash # 1. 构建基础镜像(约 30 分钟) cd /volume1/stock/sanguo_vnpy_v2 docker build -f docker/Dockerfile.base -t sanguo_vnpy:base . # 2. 构建应用镜像(约 5 分钟) docker build -f docker/Dockerfile.nas -t sanguo_vnpy:latest . # 3. 启动容器 docker run -d \ --name sanguo_vnpy_v2 \ -p 8000:8000 -p 8080:8080 \ -v /volume1/stock/sanguo_data:/app/data \ sanguo_vnpy:latest ``` #### 代码更新后(仅重建应用层) ```bash # 只需重建应用镜像(约 5 分钟) docker build -f docker/Dockerfile.nas -t sanguo_vnpy:latest . # 重启容器 docker restart sanguo_vnpy_v2 ``` ### 9.6 依赖更新后(重建基础层) ```bash # 当 requirements-docker.txt 变更时 docker build -f docker/Dockerfile.base -t sanguo_vnpy:base . docker build -f docker/Dockerfile.nas -t sanguo_vnpy:latest . ``` ### 9.7 文件结构 ``` sanguo_vnpy_v2/ ├── docker/ │ ├── Dockerfile.base # 基础镜像(依赖层) │ ├── Dockerfile.nas # 应用镜像 │ ├── entrypoint.sh # 启动脚本 │ └── requirements-docker.txt # 依赖清单 ├── sanguo_trader/ # 交易模块 ├── sanguo_web/ # Web 服务 ├── vnpy_v4.4.0/ # VeighNa 上游 └── config/ # 配置文件 ``` ## 10. 相关文档 - 需求文档: ../requirements/functional/feature-001-docker-web-deployment.md - VeighNa 文档: ../../vnpy_v4.4.0/docs/ - 构建日志: ~/build.log, ~/rebuild2.log, ~/build4.log