#!/bin/bash # DMS合规性测试工具 - 跨平台 Docker Compose部署包创建脚本 # 支持任意选择目标平台架构 # 支持FastAPI和Flask两种框架 set -e # 配置变量 EXPORT_DIR="dms-compliance-multiplatform-$(date +%Y%m%d-%H%M%S)" IMAGE_NAME="compliance-dms-multiplatform" ARCHIVE_NAME="$EXPORT_DIR.tar.gz" # 支持的平台列表 - 使用函数替代关联数组以兼容旧版bash get_platform() { case "$1" in 1) echo "linux/amd64" ;; 2) echo "linux/arm64" ;; 3) echo "linux/arm/v7" ;; 4) echo "linux/arm/v6" ;; 5) echo "linux/386" ;; 6) echo "linux/ppc64le" ;; 7) echo "linux/s390x" ;; 8) echo "linux/riscv64" ;; *) echo "" ;; esac } get_platform_name() { case "$1" in "linux/amd64") echo "AMD64 (x86_64) - Intel/AMD 64位" ;; "linux/arm64") echo "ARM64 (aarch64) - Apple M1/M2, ARM 64位" ;; "linux/arm/v7") echo "ARMv7 - 树莓派 3/4, ARM 32位" ;; "linux/arm/v6") echo "ARMv6 - 树莓派 1/Zero, ARM 32位" ;; "linux/386") echo "i386 - Intel/AMD 32位" ;; "linux/ppc64le") echo "PowerPC 64位小端" ;; "linux/s390x") echo "IBM System z" ;; "linux/riscv64") echo "RISC-V 64位" ;; *) echo "未知平台" ;; esac } # 服务架构选择 get_service_arch() { case "$1" in 1) echo "dual" ;; 2) echo "fastapi" ;; 3) echo "flask" ;; *) echo "" ;; esac } get_service_arch_name() { case "$1" in "dual") echo "双服务架构 - API服务器(5050) + 历史查看器(5051)" ;; "fastapi") echo "FastAPI单服务 - 现代异步框架,自动生成API文档(5051)" ;; "flask") echo "Flask单服务 - 轻量级传统框架(5050)" ;; *) echo "未知架构" ;; esac } get_service_ports() { case "$1" in "dual") echo "5050,5051" ;; "fastapi") echo "5051" ;; "flask") echo "5050" ;; *) echo "5050" ;; esac } echo "=== DMS合规性测试工具 跨平台 Docker Compose部署包创建脚本 ===" echo "" # 检查Docker是否运行 if ! docker info >/dev/null 2>&1; then echo "[错误] Docker未运行或无法访问" exit 1 fi # 检查是否支持buildx if ! docker buildx version >/dev/null 2>&1; then echo "[错误] Docker Buildx未安装或不可用" echo "[提示] 请升级到Docker 19.03+或安装buildx插件" exit 1 fi # 检测当前平台 CURRENT_ARCH=$(docker version --format '{{.Server.Arch}}' 2>/dev/null || uname -m) case "$CURRENT_ARCH" in x86_64|amd64) CURRENT_PLATFORM="linux/amd64" ;; aarch64|arm64) CURRENT_PLATFORM="linux/arm64" ;; armv7l) CURRENT_PLATFORM="linux/arm/v7" ;; armv6l) CURRENT_PLATFORM="linux/arm/v6" ;; i386|i686) CURRENT_PLATFORM="linux/386" ;; *) CURRENT_PLATFORM="linux/amd64" ;; esac echo "[信息] 当前平台: ${PLATFORM_NAMES[$CURRENT_PLATFORM]}" echo "" # 选择服务架构 echo "请选择服务架构:" echo " 1) $(get_service_arch_name "dual")" echo " 2) $(get_service_arch_name "fastapi")" echo " 3) $(get_service_arch_name "flask")" echo "" read -p "请输入选择 (1-3) [默认: 1]: " service_choice service_choice=${service_choice:-1} SELECTED_SERVICE_ARCH=$(get_service_arch "$service_choice") if [[ -z "$SELECTED_SERVICE_ARCH" ]]; then echo "[错误] 无效的服务架构选择" exit 1 fi SELECTED_PORTS=$(get_service_ports "$SELECTED_SERVICE_ARCH") echo "[信息] 选择的架构: $(get_service_arch_name "$SELECTED_SERVICE_ARCH")" echo "[信息] 服务端口: $SELECTED_PORTS" echo "" # 选择目标平台 echo "请选择目标平台架构:" for key in 1 2 3 4 5 6 7 8; do platform=$(get_platform "$key") name=$(get_platform_name "$platform") if [[ "$platform" == "$CURRENT_PLATFORM" ]]; then echo " $key) $name [当前平台]" else echo " $key) $name" fi done echo " 9) 多平台构建 (同时构建多个平台)" echo " 0) 自动检测当前平台" echo "" read -p "请输入选择 (0-9) [默认: 0]: " platform_choice platform_choice=${platform_choice:-0} if [[ "$platform_choice" == "0" ]]; then TARGET_PLATFORM="$CURRENT_PLATFORM" TARGET_PLATFORM_NAME="$(get_platform_name "$CURRENT_PLATFORM") [自动检测]" MULTI_PLATFORM=false elif [[ "$platform_choice" == "9" ]]; then echo "" echo "多平台构建选项:" echo " 1) 常用平台 (amd64 + arm64)" echo " 2) 全平台 (所有支持的平台)" echo " 3) 自定义选择" echo "" read -p "请选择多平台构建方式 (1-3) [默认: 1]: " multi_choice multi_choice=${multi_choice:-1} case "$multi_choice" in 1) TARGET_PLATFORM="linux/amd64,linux/arm64" TARGET_PLATFORM_NAME="常用平台 (AMD64 + ARM64)" ;; 2) TARGET_PLATFORM="linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/386,linux/ppc64le,linux/s390x,linux/riscv64" TARGET_PLATFORM_NAME="全平台构建" ;; 3) echo "" echo "请选择要构建的平台 (多选,用空格分隔):" for key in 1 2 3 4 5 6 7 8; do platform=$(get_platform "$key") echo " $key) $(get_platform_name "$platform")" done echo "" read -p "请输入选择的平台编号 (例如: 1 2 3): " selected_platforms selected_platform_list="" for num in $selected_platforms; do platform=$(get_platform "$num") if [[ -n "$platform" ]]; then if [[ -n "$selected_platform_list" ]]; then selected_platform_list="$selected_platform_list,$platform" else selected_platform_list="$platform" fi fi done if [[ -z "$selected_platform_list" ]]; then echo "[错误] 未选择有效的平台" exit 1 fi TARGET_PLATFORM="$selected_platform_list" TARGET_PLATFORM_NAME="自定义平台 ($selected_platform_list)" ;; *) echo "[错误] 无效的多平台构建选择" exit 1 ;; esac MULTI_PLATFORM=true else TARGET_PLATFORM=$(get_platform "$platform_choice") if [[ -z "$TARGET_PLATFORM" ]]; then echo "[错误] 无效的平台选择" exit 1 fi TARGET_PLATFORM_NAME=$(get_platform_name "$TARGET_PLATFORM") MULTI_PLATFORM=false fi echo "[信息] 目标平台: $TARGET_PLATFORM_NAME" echo "[信息] 多平台构建: $MULTI_PLATFORM" echo "" # 确认构建 echo "构建配置确认:" echo " 架构: $(get_service_arch_name "$SELECTED_SERVICE_ARCH")" echo " 端口: $SELECTED_PORTS" echo " 平台: $TARGET_PLATFORM_NAME" echo " 输出: $EXPORT_DIR" echo "" read -p "确认开始构建? (y/N): " confirm if [[ ! "$confirm" =~ ^[Yy]$ ]]; then echo "[信息] 构建已取消" exit 0 fi # 先创建初始导出目录(稍后会重命名) INITIAL_EXPORT_DIR="$EXPORT_DIR" echo "[信息] 准备创建导出目录..." # 更新导出目录名称以包含平台信息 if [[ "$MULTI_PLATFORM" == "true" ]]; then EXPORT_DIR="dms-compliance-${SELECTED_SERVICE_ARCH}-multiplatform-$(date +%Y%m%d-%H%M%S)" else platform_suffix=$(echo "$TARGET_PLATFORM" | sed 's/linux\///g' | sed 's/\//-/g') EXPORT_DIR="dms-compliance-${SELECTED_SERVICE_ARCH}-${platform_suffix}-$(date +%Y%m%d-%H%M%S)" fi ARCHIVE_NAME="$EXPORT_DIR.tar.gz" # 重新创建目录 rm -rf "$EXPORT_DIR" mkdir -p "$EXPORT_DIR" echo "[信息] 最终输出目录: $EXPORT_DIR" # 创建最终导出目录 rm -rf "$EXPORT_DIR" mkdir -p "$EXPORT_DIR" echo "" # 复制项目文件 echo "[步骤 1/6] 复制项目文件..." # 创建临时构建目录 TEMP_BUILD_DIR=$(mktemp -d) trap "rm -rf $TEMP_BUILD_DIR" EXIT # 复制核心目录(排除缓存和临时文件) echo "[信息] 复制核心目录..." mkdir -p "$TEMP_BUILD_DIR"/{ddms_compliance_suite,custom_stages,custom_testcases,templates,static,assets} rsync -av --exclude='__pycache__' --exclude='*.pyc' --exclude='*.log' ddms_compliance_suite/ "$TEMP_BUILD_DIR/ddms_compliance_suite/" 2>/dev/null || cp -r ddms_compliance_suite "$TEMP_BUILD_DIR/" rsync -av --exclude='__pycache__' --exclude='*.pyc' custom_stages/ "$TEMP_BUILD_DIR/custom_stages/" 2>/dev/null || cp -r custom_stages "$TEMP_BUILD_DIR/" rsync -av --exclude='__pycache__' --exclude='*.pyc' custom_testcases/ "$TEMP_BUILD_DIR/custom_testcases/" 2>/dev/null || cp -r custom_testcases "$TEMP_BUILD_DIR/" 2>/dev/null || true # 复制模板和静态文件 rsync -av templates/ "$TEMP_BUILD_DIR/templates/" 2>/dev/null || cp -r templates "$TEMP_BUILD_DIR/" 2>/dev/null || mkdir -p "$TEMP_BUILD_DIR/templates" rsync -av static/ "$TEMP_BUILD_DIR/static/" 2>/dev/null || cp -r static "$TEMP_BUILD_DIR/" 2>/dev/null || mkdir -p "$TEMP_BUILD_DIR/static" rsync -av assets/ "$TEMP_BUILD_DIR/assets/" 2>/dev/null || cp -r assets "$TEMP_BUILD_DIR/" 2>/dev/null || mkdir -p "$TEMP_BUILD_DIR/assets" # 复制核心Python文件 echo "[信息] 复制核心Python文件..." for file in api_server.py history_viewer.py flask_app.py web_interface.py; do [ -f "$file" ] && cp "$file" "$TEMP_BUILD_DIR/" done # 复制requirements.txt cp requirements.txt "$TEMP_BUILD_DIR/" # 创建对应架构的Dockerfile echo "[步骤 2/6] 创建 Dockerfile..." cd "$TEMP_BUILD_DIR" if [[ "$SELECTED_SERVICE_ARCH" == "dual" ]]; then # 双服务架构 - 使用supervisor管理两个服务 cat > "Dockerfile" << 'EOF' # 使用稳定的Python基础镜像 FROM python:3.11-alpine # 安装系统依赖 RUN apk update && apk add --no-cache \ gcc \ musl-dev \ libffi-dev \ openssl-dev \ python3-dev \ build-base \ linux-headers \ supervisor \ curl \ bash \ tzdata && \ rm -rf /var/cache/apk/* # 设置工作目录 WORKDIR /app # 复制依赖文件并安装Python包 COPY requirements.txt . RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \ pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 创建supervisor配置目录 RUN mkdir -p /etc/supervisor/conf.d /var/log/supervisor /app/logs /app/test_reports /app/uploads # 复制supervisor配置 COPY supervisord.conf /etc/supervisor/conf.d/ # 创建非root用户 RUN addgroup -g 1000 appuser && \ adduser -D -u 1000 -G appuser appuser && \ chown -R appuser:appuser /app /var/log/supervisor USER appuser # 设置环境变量 ENV PYTHONPATH=/app ENV FLASK_ENV=production ENV PYTHONUNBUFFERED=1 # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:5050/ || exit 1 # 暴露端口 EXPOSE 5050 5051 # 启动命令 CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] EOF # 创建supervisor配置 cat > "supervisord.conf" << 'EOF' [supervisord] nodaemon=true logfile=/var/log/supervisor/supervisord.log pidfile=/var/run/supervisord.pid childlogdir=/var/log/supervisor logfile_maxbytes=50MB logfile_backups=10 loglevel=info [unix_http_server] file=/tmp/supervisor.sock chmod=0700 [supervisorctl] serverurl=unix:///tmp/supervisor.sock [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface # DMS API服务器 (主服务) [program:api_server] command=python api_server.py directory=/app autostart=true autorestart=true redirect_stderr=true stdout_logfile=/var/log/supervisor/api_server.log stdout_logfile_maxbytes=10MB stdout_logfile_backups=5 environment=PYTHONPATH="/app",PYTHONUNBUFFERED="1" # 历史查看器服务 [program:history_viewer] command=python history_viewer.py directory=/app autostart=true autorestart=true redirect_stderr=true stdout_logfile=/var/log/supervisor/history_viewer.log stdout_logfile_maxbytes=10MB stdout_logfile_backups=5 environment=PYTHONPATH="/app",PYTHONUNBUFFERED="1" # 进程组配置 [group:dms_services] programs=api_server,history_viewer priority=999 EOF elif [[ "$SELECTED_SERVICE_ARCH" == "fastapi" ]]; then cat > "Dockerfile" << 'EOF' # 使用稳定的Python基础镜像 FROM python:3.11-alpine # 安装系统依赖 RUN apk update && apk add --no-cache \ gcc \ musl-dev \ libffi-dev \ openssl-dev \ python3-dev \ build-base \ linux-headers \ curl \ bash \ tzdata && \ rm -rf /var/cache/apk/* # 设置工作目录 WORKDIR /app # 复制依赖文件并安装Python包 COPY requirements.txt . RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \ pip install --no-cache-dir -r requirements.txt && \ pip install --no-cache-dir fastapi uvicorn[standard] # 复制应用代码 COPY . . # 创建必要目录 RUN mkdir -p /app/logs /app/uploads /app/reports # 创建非root用户 RUN addgroup -g 1000 appuser && \ adduser -D -u 1000 -G appuser appuser && \ chown -R appuser:appuser /app USER appuser # 设置环境变量 ENV PYTHONPATH=/app ENV PYTHONUNBUFFERED=1 # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:5051/health || exit 1 # 暴露端口 EXPOSE 5051 # 启动命令 CMD ["uvicorn", "web_interface_fastapi:app", "--host", "0.0.0.0", "--port", "5051"] EOF # 创建FastAPI web接口 cat > "web_interface_fastapi.py" << 'EOF' #!/usr/bin/env python3 """ DMS合规性测试工具 - FastAPI Web接口 """ import os import sys import json import logging import traceback from datetime import datetime from pathlib import Path from typing import Optional, Dict, Any, List from fastapi import FastAPI, HTTPException, UploadFile, File, Form, BackgroundTasks from fastapi.responses import HTMLResponse, FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel import uvicorn # 添加项目根目录到Python路径 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # 导入测试模块 try: from ddms_compliance_suite.main import main as run_compliance_test from ddms_compliance_suite.input_parser.parser import parse_dms_api_spec except ImportError as e: print(f"导入错误: {e}") sys.exit(1) # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 创建FastAPI应用 app = FastAPI( title="DMS合规性测试工具", description="用于测试DMS API合规性的Web界面", version="1.0.0", docs_url="/docs", redoc_url="/redoc" ) # 添加CORS中间件 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 数据模型 class TestRequest(BaseModel): api_spec_file: str base_url: Optional[str] = "https://www.dev.ideas.cnpc" test_mode: Optional[str] = "all" endpoint_filter: Optional[str] = None log_level: Optional[str] = "INFO" enable_well_data: Optional[bool] = True class TestResult(BaseModel): success: bool message: str log_file: Optional[str] = None details: Optional[Dict[str, Any]] = None # 全局变量 current_test_status = {"running": False, "progress": 0, "message": ""} test_results = {} @app.get("/", response_class=HTMLResponse) async def root(): """主页""" return """