Files
sanguo_vnpy_v2/docs/design/deployment/docker-web-deployment.md
T
claude_dev 918bbed0fc fix: 修复登录500错误和移除明文密码提示
- 修复 deps.py 中 get_vn_service 的引用错误 (vn_service.vn_service -> vn_service)
- 移除登录页面上的明文密码提示
- 改进前端错误处理,避免数据加载失败导致登录显示错误
2026-07-02 12:23:55 +08:00

23 KiB
Raw Blame History

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 设计

# 多阶段构建
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 配置

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 端点

# 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=["认证"])

交易接口示例

# 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 行情推送

# 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

# 1. 安装 Container Manager
# 2. 导入镜像
# 3. 创建项目
# 4. 启动容器

QNAP NAS

# 1. 安装 Container Station
# 2. 导入镜像
# 3. 创建容器
# 4. 启动

通用 Linux NAS

# 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 配置管理

# 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 数据持久化

# 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 认证方案

# 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 配置

# 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 健康检查

@app.get("/health")
async def health_check():
    """健康检查接口"""
    return {
        "status": "healthy",
        "services": {
            "database": check_database(),
            "gateway": check_gateway(),
            "strategy": check_strategy()
        }
    }

5.2 日志配置

# 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 多容器部署

# 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 数据库升级

# 使用 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:

    - polars>=1.26.0
    + polars[rtcompat]>=1.26.0
    
  2. 修改 docker/entrypoint.sh:

    #!/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

解决方案 使用清华镜像加速:

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 关键配置

# 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 配置

#!/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

# 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 (应用层)

# 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 构建流程

首次构建(全量)

# 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

代码更新后(仅重建应用层)

# 只需重建应用镜像(约 5 分钟)
docker build -f docker/Dockerfile.nas -t sanguo_vnpy:latest .

# 重启容器
docker restart sanguo_vnpy_v2

9.6 依赖更新后(重建基础层)

# 当 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