fix: 修复登录500错误和移除明文密码提示

- 修复 deps.py 中 get_vn_service 的引用错误 (vn_service.vn_service -> vn_service)
- 移除登录页面上的明文密码提示
- 改进前端错误处理,避免数据加载失败导致登录显示错误
This commit is contained in:
2026-07-02 12:23:55 +08:00
parent 3fbe6c9be5
commit 918bbed0fc
63 changed files with 16699 additions and 10 deletions
+86
View File
@@ -0,0 +1,86 @@
# Sanguo VeighNa 环境变量配置示例
# 复制此文件为 .env 并根据需要修改
# ============================================
# 数据目录配置(NAS 部署关键配置)
# ============================================
# Synology DSM 路径通常是 /volume1/...
# 请根据实际情况修改此路径
DATA_DIR=/volume1/docker/stock/sanguo_vnpy
# ============================================
# 端口配置
# ============================================
HTTP_PORT=8000
WS_PORT=8080
NGINX_HTTP_PORT=80
NGINX_HTTPS_PORT=443
# ============================================
# 时区配置
# ============================================
TZ=Asia/Shanghai
# ============================================
# VeighNa 日志配置
# ============================================
VNPY_LOG_LEVEL=INFO
VNPY_LOG_FILE=/app/logs/vnpy.log
# ============================================
# 数据库配置
# ============================================
DB_TYPE=sqlite
DB_PATH=/app/data/vnpy.db
POSTGRES_DB=vnpy
POSTGRES_USER=vnpy
POSTGRES_PASSWORD=vnpy_password_change_me
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
# ============================================
# Web 服务配置
# ============================================
WEB_HOST=0.0.0.0
WEB_PORT=8000
WEB_WORKERS=2
# ============================================
# 安全配置
# ============================================
JWT_SECRET_KEY=change-this-to-a-random-secret-key
SESSION_TIMEOUT=3600
# ============================================
# Redis 配置
# ============================================
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=
# ============================================
# Nginx 配置
# ============================================
ENABLE_NGINX=false
# ============================================
# 资源限制
# ============================================
VNPY_CPU_LIMIT=2.0
VNPY_MEMORY_LIMIT=2G
VNPY_CPU_RESERVATION=0.5
VNPY_MEMORY_RESERVATION=512M
REDIS_CPU_LIMIT=0.5
REDIS_MEMORY_LIMIT=256M
NGINX_CPU_LIMIT=0.5
NGINX_MEMORY_LIMIT=256M
# ============================================
# 开发选项
# ============================================
SKIP_REDIS_CHECK=0
USE_POSTGRES=0
DEBUG=0
+71
View File
@@ -0,0 +1,71 @@
# Sanguo VeighNa Docker 镜像 - NAS 适配版
# 使用 Python 3.10Synology NAS 已有镜像)
# 单阶段构建,简化部署流程
FROM python:3.10-slim
# 设置环境变量
ENV TZ=Asia/Shanghai \
DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
VNPY_LOG_LEVEL=INFO \
TA_LIBRARY_PATH=/usr/local/lib \
TA_INCLUDE_PATH=/usr/local/include
# 安装运行时和编译依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
build-essential \
wget \
gcc \
g++ \
make \
libc6-dev \
&& rm -rf /var/lib/apt/lists/*
# 下载并编译 TA-Lib
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
# 更新 ldconfig
RUN ldconfig
# 设置工作目录
WORKDIR /app
# 创建数据目录
RUN mkdir -p /app/data /app/logs /app/config /app/temp
# 复制 requirements 并安装依赖
COPY requirements-docker.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements-docker.txt
# 复制应用代码(包含所有 sanguo 模块)
COPY sanguo_web/ /app/sanguo_web/
COPY vnpy_v4.4.0/vnpy/ /app/vnpy/
COPY sanguo_trader/ /app/sanguo_trader/
COPY sanguo_research/ /app/sanguo_research/
COPY sanguo_data/ /app/sanguo_data/
COPY sanguo_common/ /app/sanguo_common/
# 复制启动脚本
COPY docker/entrypoint.sh /app/
RUN chmod +x /app/entrypoint.sh
# 暴露端口
EXPOSE 8000 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# 启动命令
ENTRYPOINT ["/app/entrypoint.sh"]
+59
View File
@@ -0,0 +1,59 @@
# Sanguo VeighNa 基础镜像
# 包含所有 Python 依赖,仅在依赖变更时重建
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 依赖
# --root-user-action=ignore 抑制 "running as root" 警告
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
# 安装 Web 服务基础依赖
RUN pip install --no-cache-dir --root-user-action=ignore uvicorn[standard] fastapi
# 元数据标签
LABEL build_date="2025-07-02"
LABEL python_version="3.11"
LABEL description="Sanguo VeighNa Base - Ready for application layer"
+43
View File
@@ -0,0 +1,43 @@
# Sanguo VeighNa Docker 镜像 - 本地构建版
# 使用国内 PyPI 镜像源,跳过 apt-get 和 TA-Lib 编译
FROM python:3.10-slim
# 设置环境变量
ENV TZ=Asia/Shanghai \
DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
VNPY_LOG_LEVEL=INFO \
TA_LIBRARY_PATH=/usr/local/lib \
TA_INCLUDE_PATH=/usr/local/include \
PIP_DEFAULT_TIMEOUT=300 \
# 使用清华 PyPI 镜像
PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \
PIP_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn
# 设置工作目录
WORKDIR /app
# 创建数据目录
RUN mkdir -p /app/data /app/logs /app/config /app/temp
# 复制 requirements 并安装依赖
COPY requirements-docker.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements-docker.txt
# 复制应用代码
COPY sanguo_web/ /app/sanguo_web/
COPY vnpy_v4.4.0/vnpy/ /app/vnpy/
COPY run_web.py /app/
# 复制启动脚本
COPY docker/entrypoint.sh /app/
RUN chmod +x /app/entrypoint.sh
# 暴露端口
EXPOSE 8000 8080
# 启动命令
ENTRYPOINT ["/app/entrypoint.sh"]
+35
View File
@@ -0,0 +1,35 @@
# Sanguo VeighNa 应用镜像
# 基于基础镜像,只包含应用代码
FROM sanguo_vnpy:base
LABEL maintainer="sanguo"
LABEL description="Sanguo VeighNa Application Layer"
# 设置工作目录
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/
# 复制启动脚本
COPY docker/entrypoint.sh .
RUN chmod +x entrypoint.sh
# 创建数据目录
RUN mkdir -p /app/data /app/logs
# 暴露端口
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"]
+160
View File
@@ -0,0 +1,160 @@
#!/bin/bash
# Sanguo VeighNa 分层构建脚本
# 用法: ./docker/build.sh [base|app|all|clean]
set -e
# 配置
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
REGISTRY="${REGISTRY:-sanguo_vnpy}"
BASE_TAG="${REGISTRY}:base"
APP_TAG="${REGISTRY}:latest"
# 颜色输出
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查 Docker
check_docker() {
if ! command -v docker &> /dev/null; then
log_error "Docker 未安装"
exit 1
fi
log_info "Docker 版本: $(docker --version)"
}
# 构建基础镜像
build_base() {
log_info "开始构建基础镜像..."
log_info "预计时间: 20-30 分钟"
cd "$PROJECT_DIR"
docker build \
-f docker/Dockerfile.base \
-t "$BASE_TAG" \
--build-arg PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \
--build-arg PIP_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn \
.
log_info "基础镜像构建完成: $BASE_TAG"
docker images "$BASE_TAG"
}
# 构建应用镜像
build_app() {
log_info "开始构建应用镜像..."
log_info "预计时间: 3-5 分钟"
cd "$PROJECT_DIR"
# 检查基础镜像
if ! docker image inspect "$BASE_TAG" &> /dev/null; then
log_warn "基础镜像不存在,先构建基础镜像"
build_base
fi
docker build \
-f docker/Dockerfile.nas \
-t "$APP_TAG" \
.
log_info "应用镜像构建完成: $APP_TAG"
docker images "$APP_TAG"
}
# 全量构建
build_all() {
log_info "开始全量构建..."
build_base
build_app
}
# 清理旧镜像
clean_images() {
log_info "清理旧镜像..."
# 删除悬空镜像
docker image prune -f
# 询问是否删除旧版本
read -p "是否删除旧版本镜像? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
docker images "$REGISTRY" --format "{{.ID}} {{.Tag}}" | \
grep -v "$BASE_TAG\|$APP_TAG" | \
awk '{print $1}' | \
xargs -r docker rmi
log_info "旧镜像已清理"
fi
}
# 显示帮助
show_help() {
cat << EOF
Sanguo VeighNa 分层构建脚本
用法: $0 [命令]
命令:
base 构建基础镜像 (依赖层,约30分钟)
app 构建应用镜像 (应用层,约5分钟)
all 全量构建 (base + app)
clean 清理旧镜像
help 显示此帮助
示例:
$0 base # 首次构建或依赖更新时
$0 app # 代码更新后快速重建
$0 all # 完整构建
$0 clean # 清理旧镜像
环境变量:
REGISTRY 镜像前缀 (默认: sanguo_vnpy)
EOF
}
# 主逻辑
main() {
check_docker
case "${1:-help}" in
base)
build_base
;;
app)
build_app
;;
all)
build_all
;;
clean)
clean_images
;;
help|--help|-h)
show_help
;;
*)
log_error "未知命令: $1"
show_help
exit 1
;;
esac
}
main "$@"
+282
View File
@@ -0,0 +1,282 @@
#!/bin/bash
# Sanguo VeighNa Synology NAS 快速部署脚本
# 用于在 Synology NAS 上快速部署和配置
set -e
# ============================================
# 颜色定义
# ============================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ============================================
# 日志函数
# ============================================
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $1"
}
# ============================================
# 打印 Banner
# ============================================
print_banner() {
cat << "EOF"
╔════════════════════════════════════════════════════════════╗
║ ║
║ _____ ____ ____ _ _____ _ ___ _ ║
| __ \ / __ \ / __ \ | ||_ _|__ _| | |_ _|___| |
| |__) | | | | | | |_| | |/ _` | |_____|| | _ \ |
| ___/| | | | | | |_ | | (_| | |_____| | || |||
|_| | |__| | \___/\__| |\_____|_| |___||_||_|
\____/ ║
║ ║
║ Synology NAS 部署脚本 v1.0.0 ║
║ ║
╚════════════════════════════════════════════════════════════╝
EOF
}
# ============================================
# 检查权限
# ============================================
check_permission() {
log_step "检查执行权限..."
if [ "$EUID" -ne 0 ]; then
log_error "请使用 sudo 运行此脚本"
echo "用法: sudo bash deploy-synology.sh"
exit 1
fi
log_info "权限检查通过"
}
# ============================================
# 加载配置
# ============================================
load_config() {
log_step "加载配置..."
# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 设置默认值
DATA_DIR=${DATA_DIR:-/volume1/docker/stock/sanguo_vnpy}
HTTP_PORT=${HTTP_PORT:-8000}
WS_PORT=${WS_PORT:-8080}
TZ=${TZ:-Asia/Shanghai}
# 尝试加载 .env 文件
if [ -f "$SCRIPT_DIR/.env" ]; then
log_info "从 .env 文件加载配置..."
source "$SCRIPT_DIR/.env"
else
log_warn ".env 文件不存在,使用默认配置"
if [ -f "$SCRIPT_DIR/.env.example" ]; then
log_info "从 .env.example 创建 .env 文件..."
cp "$SCRIPT_DIR/.env.example" "$SCRIPT_DIR/.env"
log_warn "请编辑 .env 文件配置您的环境"
log_info "配置完成后重新运行此脚本"
exit 0
fi
fi
log_info "配置加载完成"
log_info "数据目录: $DATA_DIR"
log_info "HTTP 端口: $HTTP_PORT"
log_info "WebSocket 端口: $WS_PORT"
log_info "时区: $TZ"
}
# ============================================
# 检查 Docker
# ============================================
check_docker() {
log_step "检查 Docker 环境..."
if ! command -v docker &> /dev/null; then
log_error "Docker 未安装"
log_info "请在 DSM 中安装 Container Manager 套件"
exit 1
fi
if ! command -v docker-compose &> /dev/null; then
log_error "Docker Compose 未安装"
exit 1
fi
log_info "Docker 环境检查通过"
}
# ============================================
# 创建数据目录
# ============================================
create_directories() {
log_step "创建数据目录..."
# 主目录
mkdir -p "$DATA_DIR"
# 子目录
mkdir -p "$DATA_DIR/data"
mkdir -p "$DATA_DIR/logs"
mkdir -p "$DATA_DIR/config"
mkdir -p "$DATA_DIR/temp"
mkdir -p "$DATA_DIR/redis"
mkdir -p "$DATA_DIR/nginx"
mkdir -p "$DATA_DIR/nginx/ssl"
# 设置权限
chmod -R 755 "$DATA_DIR"
log_info "数据目录创建完成: $DATA_DIR"
}
# ============================================
# 备份现有数据
# ============================================
backup_data() {
log_step "检查是否需要备份..."
if [ -d "$DATA_DIR/data" ] && [ "$(ls -A $DATA_DIR/data 2>/dev/null)" ]; then
BACKUP_DIR="/volume1/Backup"
mkdir -p "$BACKUP_DIR"
BACKUP_FILE="$BACKUP_DIR/vnpy_backup_$(date +%Y%m%d_%H%M%S).tar.gz"
log_warn "检测到现有数据,创建备份..."
tar czf "$BACKUP_FILE" -C "$DATA_DIR" .
log_info "备份已创建: $BACKUP_FILE"
log_info "备份大小: $(du -h $BACKUP_FILE | cut -f1)"
else
log_info "未检测到现有数据,跳过备份"
fi
}
# ============================================
# 构建 Docker 镜像
# ============================================
build_image() {
log_step "构建 Docker 镜像..."
cd "$SCRIPT_DIR"
log_info "开始构建(这可能需要几分钟)..."
docker-compose build
log_info "Docker 镜像构建完成"
}
# ============================================
# 启动服务
# ============================================
start_services() {
log_step "启动服务..."
cd "$SCRIPT_DIR"
# 停止现有服务(如果有)
if docker-compose ps | grep -q "Up"; then
log_warn "检测到运行中的服务,先停止..."
docker-compose down
fi
# 启动服务
log_info "启动服务..."
docker-compose up -d
log_info "服务启动完成"
}
# ============================================
# 检查服务状态
# ============================================
check_status() {
log_step "检查服务状态..."
cd "$SCRIPT_DIR"
# 等待服务启动
sleep 5
# 检查容器状态
docker-compose ps
echo ""
log_info "检查服务日志(最后 20 行):"
docker-compose logs --tail=20 vnpy
}
# ============================================
# 显示访问信息
# ============================================
show_access_info() {
log_step "部署完成!"
echo ""
echo "=========================================="
log_info "访问地址:"
echo ""
echo " HTTP: http://$(hostname -I | awk '{print $1}'):$HTTP_PORT"
echo " WebSocket: ws://$(hostname -I | awk '{print $1}'):$WS_PORT"
echo ""
log_info "默认登录凭据:"
echo ""
echo " 用户名: admin"
echo " 密码: admin123"
echo ""
log_warn "重要提示:"
echo " 1. 请立即修改默认密码"
echo " 2. 数据目录: $DATA_DIR"
echo " 3. 查看日志: docker-compose logs -f"
echo " 4. 停止服务: docker-compose down"
echo "=========================================="
echo ""
}
# ============================================
# 主函数
# ============================================
main() {
print_banner
echo ""
check_permission
load_config
check_docker
create_directories
backup_data
build_image
start_services
check_status
show_access_info
log_info "部署脚本执行完成!"
}
# ============================================
# 信号处理
# ============================================
trap 'log_error "脚本被中断"; exit 1' SIGINT SIGTERM
# ============================================
# 执行主函数
# ============================================
main "$@"
+262
View File
@@ -0,0 +1,262 @@
version: '3.8'
# Sanguo VeighNa Docker 编排配置
# 用于 NAS 部署和开发环境
# 支持环境变量配置和绑定挂载
# 加载环境变量
# 使用前请复制 .env.example 为 .env 并修改配置
services:
# ============================================
# VeighNa 核心服务
# ============================================
vnpy:
build:
context: ..
dockerfile: docker/Dockerfile
container_name: sanguo-vnpy
restart: unless-stopped
# 容器名称
hostname: vnpy-server
# 环境变量
environment:
# Python 配置
- PYTHONUNBUFFERED=1
- PYTHONDONTWRITEBYTECODE=1
- TZ=${TZ:-Asia/Shanghai}
# VeighNa 配置
- VNPY_LOG_LEVEL=${VNPY_LOG_LEVEL:-INFO}
- VNPY_LOG_FILE=${VNPY_LOG_FILE:-/app/logs/vnpy.log}
# 数据库配置
- DB_TYPE=${DB_TYPE:-sqlite}
- DB_PATH=${DB_PATH:-/app/data/vnpy.db}
# Web 服务配置
- WEB_HOST=${WEB_HOST:-0.0.0.0}
- WEB_PORT=${WEB_PORT:-8000}
- WEB_WORKERS=${WEB_WORKERS:-2}
# 安全配置
- JWT_SECRET_KEY=${JWT_SECRET_KEY:-change-this-to-a-random-secret-key}
- SESSION_TIMEOUT=${SESSION_TIMEOUT:-3600}
# Redis 配置
- REDIS_HOST=${REDIS_HOST:-redis}
- REDIS_PORT=${REDIS_PORT:-6379}
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
# PostgreSQL 配置
- POSTGRES_DB=${POSTGRES_DB:-vnpy}
- POSTGRES_USER=${POSTGRES_USER:-vnpy}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-vnpy_password_change_me}
- POSTGRES_HOST=${POSTGRES_HOST:-postgres}
- POSTGRES_PORT=${POSTGRES_PORT:-5432}
# 开发选项
- SKIP_REDIS_CHECK=${SKIP_REDIS_CHECK:-0}
- USE_POSTGRES=${USE_POSTGRES:-0}
- DEBUG=${DEBUG:-0}
# 端口映射
ports:
- "${HTTP_PORT:-8000}:8000" # HTTP API
- "${WS_PORT:-8080}:8080" # WebSocket
# 数据卷 - 使用绑定挂载到 NAS stock 目录
volumes:
# 数据持久化目录 - 挂载到 NAS stock 目录
- ${DATA_DIR}/data:/app/data
- ${DATA_DIR}/logs:/app/logs
- ${DATA_DIR}/config:/app/config
- ${DATA_DIR}/temp:/app/temp
# 配置文件(只读)- 如果有外部配置
- ../config:/app/config:ro
# 网络
networks:
- vnpy-network
# 依赖项
depends_on:
- redis
# 资源限制
deploy:
resources:
limits:
cpus: '${VNPY_CPU_LIMIT:-2.0}'
memory: ${VNPY_MEMORY_LIMIT:-2G}
reservations:
cpus: '${VNPY_CPU_RESERVATION:-0.5}'
memory: ${VNPY_MEMORY_RESERVATION:-512M}
# ============================================
# Redis 服务(缓存和消息队列)
# ============================================
redis:
image: redis:7-alpine
container_name: sanguo-redis
restart: unless-stopped
# 命令参数
command: >
sh -c '
if [ -n "${REDIS_PASSWORD}" ]; then
redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
else
redis-server --appendonly yes
fi
'
# 环境变量
environment:
- TZ=${TZ:-Asia/Shanghai}
# 数据卷 - 挂载到 NAS stock 目录
volumes:
- ${DATA_DIR}/redis:/data
# 网络
networks:
- vnpy-network
# 资源限制
deploy:
resources:
limits:
cpus: '${REDIS_CPU_LIMIT:-0.5}'
memory: ${REDIS_MEMORY_LIMIT:-256M}
# ============================================
# Nginx 反向代理(可选)
# ============================================
nginx:
image: nginx:alpine
container_name: sanguo-nginx
restart: unless-stopped
profiles:
- nginx
- ${ENABLE_NGINX:+nginx}
# 环境变量
environment:
- TZ=${TZ:-Asia/Shanghai}
# 端口映射
ports:
- "${NGINX_HTTP_PORT:-80}:80"
- "${NGINX_HTTPS_PORT:-443}:443"
# 配置文件
volumes:
- ../docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ${DATA_DIR}/nginx/ssl:/etc/nginx/ssl:ro
- ${DATA_DIR}/nginx/cache:/var/cache/nginx
- ${DATA_DIR}/logs/nginx:/var/log/nginx
# 依赖项
depends_on:
- vnpy
# 网络
networks:
- vnpy-network
# 资源限制
deploy:
resources:
limits:
cpus: '${NGINX_CPU_LIMIT:-0.5}'
memory: ${NGINX_MEMORY_LIMIT:-256M}
# ============================================
# PostgreSQL 数据库(可选,升级用)
# ============================================
postgres:
image: postgres:15-alpine
container_name: sanguo-postgres
restart: unless-stopped
profiles:
- postgres
- ${USE_POSTGRES:+postgres}
# 环境变量
environment:
- POSTGRES_DB=${POSTGRES_DB:-vnpy}
- POSTGRES_USER=${POSTGRES_USER:-vnpy}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-vnpy_password_change_me}
- TZ=${TZ:-Asia/Shanghai}
- PGDATA=/var/lib/postgresql/data/pgdata
# 数据卷 - 挂载到 NAS stock 目录
volumes:
- ${DATA_DIR}/postgres:/var/lib/postgresql/data
# 网络
networks:
- vnpy-network
# 资源限制
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
# ============================================
# 网络定义
# ============================================
networks:
vnpy-network:
name: sanguo_vnpy_network
driver: bridge
# ============================================
# 使用说明
# ============================================
#
# 基本使用:
#
# 1. 首次使用前,配置环境变量:
# cp .env.example .env
# vi .env # 修改 DATA_DIR 等配置
#
# 2. 创建数据目录:
# mkdir -p ${DATA_DIR}/{data,logs,config,temp,redis,nginx}
#
# 3. 启动核心服务:
# docker-compose up -d
#
# 4. 启动所有服务(含 Nginx):
# docker-compose --profile nginx up -d
#
# 5. 启动所有服务(含 PostgreSQL:
# docker-compose --profile postgres up -d
#
# 管理命令:
#
# 查看日志:
# docker-compose logs -f vnpy
#
# 停止服务:
# docker-compose down
#
# 重启服务:
# docker-compose restart vnpy
#
# 进入容器:
# docker-compose exec vnpy bash
#
# 更新并重启:
# docker-compose up -d --build
#
# 数据备份:
# tar czf vnpy_backup_$(date +%Y%m%d).tar.gz ${DATA_DIR}
#
# ============================================
+340
View File
@@ -0,0 +1,340 @@
#!/bin/bash
# Sanguo VeighNa Docker 容器启动脚本
set -e
# ============================================
# 颜色定义
# ============================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ============================================
# 日志函数
# ============================================
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $1"
}
# ============================================
# 打印启动信息
# ============================================
print_banner() {
cat << "EOF"
╔════════════════════════════════════════════════════════════╗
║ ║
║ _____ ____ ____ _ _____ _ ___ _ ║
| __ \ / __ \ / __ \ | ||_ _|__ _| | |_ _|___| |
| |__) | | | | | | |_| | |/ _` | |_____|| | _ \ |
| ___/| | | | | | |_ | | (_| | |_____| | || |||
|_| | |__| | \___/\__| |\_____|_| |___||_||_|
\____/ ║
║ ║
║ Docker Web 版本 v1.0.0 ║
║ 基于 VeighNa 4.4.0 构建 ║
║ ║
╚════════════════════════════════════════════════════════════╝
EOF
}
# ============================================
# 检查环境变量
# ============================================
check_env() {
log_step "检查环境变量..."
# 设置默认值
export TZ="${TZ:-Asia/Shanghai}"
export PYTHONUNBUFFERED="${PYTHONUNBUFFERED:-1}"
export PYTHONDONTWRITEBYTECODE="${PYTHONDONTWRITEBYTECODE:-1}"
# VeighNa 配置
export VNPY_LOG_LEVEL="${VNPY_LOG_LEVEL:-INFO}"
export VNPY_LOG_FILE="${VNPY_LOG_FILE:-/app/logs/vnpy.log}"
# 数据库配置
export DB_TYPE="${DB_TYPE:-sqlite}"
export DB_PATH="${DB_PATH:-/app/data/vnpy.db}"
# Web 服务配置
export WEB_HOST="${WEB_HOST:-0.0.0.0}"
export WEB_PORT="${WEB_PORT:-8000}"
export WEB_WORKERS="${WEB_WORKERS:-2}"
# 安全配置
export JWT_SECRET_KEY="${JWT_SECRET_KEY:-change-this-to-a-random-secret-key}"
export SESSION_TIMEOUT="${SESSION_TIMEOUT:-3600}"
# Redis 配置
export REDIS_HOST="${REDIS_HOST:-redis}"
export REDIS_PORT="${REDIS_PORT:-6379}"
# 开发选项
export SKIP_REDIS_CHECK="${SKIP_REDIS_CHECK:-0}"
export USE_POSTGRES="${USE_POSTGRES:-0}"
export DEBUG="${DEBUG:-0}"
# 打印关键配置
log_info "时区: $TZ"
log_info "日志级别: $VNPY_LOG_LEVEL"
log_info "数据库类型: $DB_TYPE"
log_info "Web 端口: $WEB_PORT"
log_info "Workers: $WEB_WORKERS"
log_info "环境变量检查完成"
}
# ============================================
# 创建数据目录
# ============================================
create_directories() {
log_step "创建数据目录..."
# 创建主要目录
mkdir -p /app/data
mkdir -p /app/logs
mkdir -p /app/temp
mkdir -p /app/config
# 创建日志子目录
mkdir -p /app/logs/web
mkdir -p /app/logs/vnpy
# 创建数据子目录
mkdir -p /app/data/database
mkdir -p /app/data/storage
# 设置权限
chmod -R 755 /app/data
chmod -R 755 /app/logs
chmod -R 755 /app/temp
log_info "数据目录创建完成"
}
# ============================================
# 初始化数据库
# ============================================
init_database() {
log_step "初始化数据库..."
if [ "$DB_TYPE" = "sqlite" ]; then
if [ ! -f /app/data/vnpy.db ]; then
log_info "创建 SQLite 数据库目录..."
mkdir -p /app/data/database
# 数据库初始化将在 Web 服务启动时完成
log_info "SQLite 数据库准备完成"
else
log_info "SQLite 数据库已存在"
fi
elif [ "$DB_TYPE" = "postgres" ] && [ "$USE_POSTGRES" = "1" ]; then
log_info "使用 PostgreSQL 数据库"
# PostgreSQL 配置由环境变量提供
else
log_warn "未知的数据库类型: $DB_TYPE,使用默认 SQLite"
export DB_TYPE=sqlite
fi
}
# ============================================
# 生成 SSL 证书(自签名,用于测试)
# ============================================
generate_ssl_cert() {
if [ ! -f /etc/nginx/ssl/cert.pem ]; then
log_warn "SSL 证书不存在,生成自签名证书..."
mkdir -p /etc/nginx/ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/key.pem \
-out /etc/nginx/ssl/cert.pem \
-subj "/C=CN/ST=Beijing/L=Beijing/O=Sanguo/CN=localhost" \
2>/dev/null
log_warn "自签名证书已生成,请勿用于生产环境"
fi
}
# ============================================
# 等待依赖服务
# ============================================
wait_for_services() {
log_step "等待依赖服务启动..."
# 等待 Redis
if [ "$SKIP_REDIS_CHECK" != "1" ]; then
log_info "等待 Redis 服务..."
REDIS_MAX_WAIT=30
REDIS_WAIT_COUNT=0
while ! nc -z ${REDIS_HOST:-redis} ${REDIS_PORT:-6379} 2>/dev/null; do
REDIS_WAIT_COUNT=$((REDIS_WAIT_COUNT + 1))
if [ $REDIS_WAIT_COUNT -ge $REDIS_MAX_WAIT ]; then
log_error "Redis 连接超时"
log_info "设置 SKIP_REDIS_CHECK=1 可跳过此检查(开发环境)"
exit 1
fi
sleep 1
done
log_info "Redis 服务已就绪 (${REDIS_HOST}:${REDIS_PORT})"
else
log_warn "跳过 Redis 检查"
fi
# 等待 PostgreSQL(如果启用)
if [ "$USE_POSTGRES" = "1" ]; then
log_info "等待 PostgreSQL 服务..."
POSTGRES_MAX_WAIT=30
POSTGRES_WAIT_COUNT=0
while ! nc -z ${POSTGRES_HOST:-postgres} ${POSTGRES_PORT:-5432} 2>/dev/null; do
POSTGRES_WAIT_COUNT=$((POSTGRES_WAIT_COUNT + 1))
if [ $POSTGRES_WAIT_COUNT -ge $POSTGRES_MAX_WAIT ]; then
log_error "PostgreSQL 连接超时"
exit 1
fi
sleep 1
done
log_info "PostgreSQL 服务已就绪"
fi
}
# ============================================
# 健康检查
# ============================================
health_check() {
log_step "执行健康检查..."
# 检查 Python 环境
if ! python --version &>/dev/null; then
log_error "Python 环境检查失败"
exit 1
fi
log_info "Python 版本: $(python --version)"
# 检查关键模块
python -c "import sys; print(f'Python 路径: {sys.executable}')" || {
log_error "Python 系统检查失败"
exit 1
}
# 检查数据目录
if [ ! -d "/app/data" ]; then
log_error "数据目录不存在: /app/data"
exit 1
fi
log_info "健康检查完成"
}
# ============================================
# 启动 Web 服务
# ============================================
start_web_service() {
log_step "启动 Web 服务..."
# 切换到工作目录
cd /app
# 确定 uvicorn 参数
UVICORN_ARGS="--host ${WEB_HOST} --port ${WEB_PORT}"
# Worker 配置
if [ "${WEB_WORKERS:-2}" -gt 1 ]; then
log_info "使用多 Worker 模式: ${WEB_WORKERS} workers"
UVICORN_ARGS="$UVICORN_ARGS --workers ${WEB_WORKERS}"
else
log_info "使用单 Worker 模式"
fi
# 日志级别
UVICORN_ARGS="$UVICORN_ARGS --log-level ${VNPY_LOG_LEVEL:-info}"
# 访问日志
UVICORN_ARGS="$UVICORN_ARGS --access-log"
# 调试模式(仅开发环境)
if [ "$DEBUG" = "1" ]; then
log_warn "启用调试模式(不应在生产环境使用)"
UVICORN_ARGS="$UVICORN_ARGS --reload"
fi
# 启动服务
log_info "启动命令: uvicorn sanguo_web.api:app $UVICORN_ARGS"
exec uvicorn sanguo_web.api:app $UVICORN_ARGS
}
# ============================================
# 显示访问信息
# ============================================
show_access_info() {
echo ""
log_info "=========================================="
log_info "服务启动中..."
log_info "=========================================="
echo ""
log_info "访问地址:"
echo ""
echo " HTTP: http://localhost:${WEB_PORT:-8000}"
echo " WebSocket: ws://localhost:${WS_PORT:-8080}"
echo ""
log_info "默认登录:"
echo ""
echo " 用户名: admin"
echo " 密码: admin123"
echo ""
log_warn "请在首次登录后修改默认密码!"
echo ""
log_info "=========================================="
echo ""
}
# ============================================
# 主函数
# ============================================
main() {
print_banner
echo ""
check_env
create_directories
init_database
generate_ssl_cert
wait_for_services
health_check
show_access_info
log_info "所有检查完成,启动服务..."
echo ""
start_web_service
}
# ============================================
# 信号处理
# ============================================
trap 'log_info "收到退出信号,优雅关闭..."; exit 0' SIGTERM SIGINT
# ============================================
# 执行主函数
# ============================================
main "$@"
+196
View File
@@ -0,0 +1,196 @@
# Sanguo VeighNa Nginx 配置
# 用于反向代理、HTTPS 和静态文件服务
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format json '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log json;
# 性能优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
# 缓存配置
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=vnpy_cache:10m max_size=1g inactive=60m use_temp_path=off;
# ============================================
# HTTP 服务器 - 重定向到 HTTPS
# ============================================
server {
listen 80;
server_name _;
# 允许健康检查
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# 其他请求重定向到 HTTPS
location / {
return 301 https://$host$request_uri;
}
}
# ============================================
# HTTPS 服务器
# ============================================
server {
listen 443 ssl http2;
server_name _;
# SSL 证书配置
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
# SSL 安全配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# ============================================
# API 代理
# ============================================
location /api/ {
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;
# 超时配置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# ============================================
# WebSocket 代理
# ============================================
location /ws/ {
proxy_pass http://vnpy:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_connect_timeout 7d;
proxy_send_timeout 7d;
proxy_read_timeout 7d;
}
# ============================================
# 健康检查
# ============================================
location /health {
proxy_pass http://vnpy:8000/health;
access_log off;
}
# ============================================
# 静态文件
# ============================================
location /static/ {
alias /app/static/;
expires 7d;
add_header Cache-Control "public, immutable";
}
location /favicon.ico {
access_log off;
log_not_found off;
}
# ============================================
# 根路径 - 前端应用
# ============================================
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;
}
# ============================================
# 安全:拒绝访问敏感文件
# ============================================
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~ ~\.(git|svn|env)$ {
deny all;
access_log off;
log_not_found off;
}
}
# ============================================
# 流控制(可选)
# ============================================
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=ws_limit:10m rate=5r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# 应用限流
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 10;
}
location /ws/ {
limit_req zone=ws_limit burst=10 nodelay;
limit_conn conn_limit 5;
}
}