compliance/create-compose-package-multiplatform.sh
2025-08-27 16:29:44 +08:00

1297 lines
36 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 """
<!DOCTYPE html>
<html>
<head>
<title>DMS合规性测试工具</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 800px; margin: 0 auto; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input, select, textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #0056b3; }
.result { margin-top: 20px; padding: 15px; border-radius: 4px; }
.success { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; }
.error { background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; }
.info { background: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460; }
.progress { width: 100%; background: #f0f0f0; border-radius: 4px; margin: 10px 0; }
.progress-bar { height: 20px; background: #007bff; border-radius: 4px; transition: width 0.3s; }
</style>
</head>
<body>
<div class="container">
<h1>DMS合规性测试工具</h1>
<p>上传API规范文件并运行合规性测试</p>
<form id="testForm" enctype="multipart/form-data">
<div class="form-group">
<label for="api_spec_file">API规范文件 (JSON):</label>
<input type="file" id="api_spec_file" name="api_spec_file" accept=".json" required>
</div>
<div class="form-group">
<label for="base_url">基础URL:</label>
<input type="text" id="base_url" name="base_url" value="https://www.dev.ideas.cnpc">
</div>
<div class="form-group">
<label for="test_mode">测试模式:</label>
<select id="test_mode" name="test_mode">
<option value="all">全部测试</option>
<option value="scenario_only">仅场景测试</option>
<option value="testcase_only">仅测试用例</option>
</select>
</div>
<div class="form-group">
<label for="endpoint_filter">端点过滤器 (可选):</label>
<input type="text" id="endpoint_filter" name="endpoint_filter" placeholder="例如: POST /api/dms/...">
</div>
<div class="form-group">
<label for="log_level">日志级别:</label>
<select id="log_level" name="log_level">
<option value="INFO">INFO</option>
<option value="DEBUG">DEBUG</option>
<option value="WARNING">WARNING</option>
<option value="ERROR">ERROR</option>
</select>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="enable_well_data" name="enable_well_data" checked>
启用井数据增强
</label>
</div>
<button type="submit">开始测试</button>
</form>
<div id="progress" style="display: none;">
<h3>测试进行中...</h3>
<div class="progress">
<div id="progressBar" class="progress-bar" style="width: 0%"></div>
</div>
<p id="progressMessage">准备中...</p>
</div>
<div id="result"></div>
<div style="margin-top: 40px;">
<h3>API文档</h3>
<p>
<a href="/docs" target="_blank">Swagger UI</a> |
<a href="/redoc" target="_blank">ReDoc</a>
</p>
</div>
</div>
<script>
document.getElementById('testForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const progressDiv = document.getElementById('progress');
const resultDiv = document.getElementById('result');
const progressBar = document.getElementById('progressBar');
const progressMessage = document.getElementById('progressMessage');
progressDiv.style.display = 'block';
resultDiv.innerHTML = '';
try {
const response = await fetch('/test', {
method: 'POST',
body: formData
});
const result = await response.json();
progressDiv.style.display = 'none';
if (result.success) {
resultDiv.innerHTML = `
<div class="result success">
<h3>测试完成</h3>
<p>${result.message}</p>
${result.log_file ? `<p><a href="/logs/${result.log_file}" target="_blank">查看详细日志</a></p>` : ''}
</div>
`;
} else {
resultDiv.innerHTML = `
<div class="result error">
<h3>测试失败</h3>
<p>${result.message}</p>
${result.details ? `<pre>${JSON.stringify(result.details, null, 2)}</pre>` : ''}
</div>
`;
}
} catch (error) {
progressDiv.style.display = 'none';
resultDiv.innerHTML = `
<div class="result error">
<h3>请求失败</h3>
<p>${error.message}</p>
</div>
`;
}
});
// 模拟进度更新
function updateProgress() {
fetch('/status')
.then(response => response.json())
.then(data => {
if (data.running) {
document.getElementById('progressBar').style.width = data.progress + '%';
document.getElementById('progressMessage').textContent = data.message;
setTimeout(updateProgress, 1000);
}
})
.catch(() => {});
}
</script>
</body>
</html>
"""
@app.get("/health")
async def health_check():
"""健康检查端点"""
return {"status": "healthy", "timestamp": datetime.now().isoformat()}
@app.get("/status")
async def get_status():
"""获取当前测试状态"""
return current_test_status
@app.post("/test")
async def run_test(
background_tasks: BackgroundTasks,
api_spec_file: UploadFile = File(...),
base_url: str = Form("https://www.dev.ideas.cnpc"),
test_mode: str = Form("all"),
endpoint_filter: Optional[str] = Form(None),
log_level: str = Form("INFO"),
enable_well_data: bool = Form(True)
):
"""运行合规性测试"""
if current_test_status["running"]:
raise HTTPException(status_code=400, detail="测试正在进行中,请等待完成")
# 保存上传的文件
upload_dir = Path("uploads")
upload_dir.mkdir(exist_ok=True)
file_path = upload_dir / api_spec_file.filename
with open(file_path, "wb") as f:
content = await api_spec_file.read()
f.write(content)
# 验证文件格式
try:
with open(file_path, 'r', encoding='utf-8') as f:
json.load(f)
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="无效的JSON文件")
# 启动后台测试任务
background_tasks.add_task(
run_test_background,
str(file_path),
base_url,
test_mode,
endpoint_filter,
log_level,
enable_well_data
)
return {"success": True, "message": "测试已开始,请等待完成"}
async def run_test_background(
api_spec_file: str,
base_url: str,
test_mode: str,
endpoint_filter: Optional[str],
log_level: str,
enable_well_data: bool
):
"""后台运行测试"""
global current_test_status, test_results
current_test_status = {"running": True, "progress": 0, "message": "初始化测试..."}
try:
# 构建命令行参数
args = [
"--api-spec-file", api_spec_file,
"--base-url", base_url,
"--test-mode", test_mode,
"--log-level", log_level
]
if endpoint_filter:
args.extend(["--endpoint-filter", endpoint_filter])
if enable_well_data:
args.append("--enable-well-data")
current_test_status["progress"] = 20
current_test_status["message"] = "解析API规范..."
# 运行测试
current_test_status["progress"] = 50
current_test_status["message"] = "执行测试..."
# 这里应该调用实际的测试函数
# result = run_compliance_test(args)
current_test_status["progress"] = 100
current_test_status["message"] = "测试完成"
test_results[api_spec_file] = {
"success": True,
"message": "测试成功完成",
"timestamp": datetime.now().isoformat()
}
except Exception as e:
logger.error(f"测试执行失败: {e}")
test_results[api_spec_file] = {
"success": False,
"message": f"测试失败: {str(e)}",
"timestamp": datetime.now().isoformat(),
"traceback": traceback.format_exc()
}
finally:
current_test_status["running"] = False
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=5051)
EOF
else
# Flask版本的Dockerfile
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 flask gunicorn
# 复制应用代码
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 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
# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:5050", "--workers", "1", "--timeout", "300", "web_interface:app"]
EOF
# 复制现有的Flask web接口
if [[ -f "../web_interface.py" ]]; then
cp "../web_interface.py" .
else
echo "[警告] web_interface.py 不存在请确保Flask版本的web接口文件存在"
fi
fi
cd ..
# 复制构建文件到最终目录
echo "[步骤 3/6] 复制构建文件..."
# 确保目标目录存在
mkdir -p "$EXPORT_DIR"
cp -r "$TEMP_BUILD_DIR"/* "$EXPORT_DIR/"
# 创建Docker Compose文件
echo "[步骤 4/6] 创建 Docker Compose 配置..."
if [[ "$SELECTED_SERVICE_ARCH" == "dual" ]]; then
# 双服务架构
cat > "$EXPORT_DIR/docker-compose.yml" << EOF
version: '3.8'
services:
dms-compliance:
build:
context: .
dockerfile: Dockerfile
platforms:
- $TARGET_PLATFORM
image: $IMAGE_NAME:latest
container_name: dms-compliance-tool
ports:
- "5050:5050" # API服务器端口
- "5051:5051" # 历史查看器端口
environment:
- PYTHONPATH=/app
- TZ=Asia/Shanghai
- FLASK_ENV=production
- PYTHONUNBUFFERED=1
volumes:
- ./uploads:/app/uploads
- ./logs:/app/logs
- ./test_reports:/app/test_reports
- ./config:/app/config:ro
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5050/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- dms-network
networks:
dms-network:
driver: bridge
volumes:
uploads:
logs:
test_reports:
config:
EOF
else
# 单服务架构
PRIMARY_PORT=$(echo "$SELECTED_PORTS" | cut -d',' -f1)
cat > "$EXPORT_DIR/docker-compose.yml" << EOF
version: '3.8'
services:
dms-compliance:
build:
context: .
dockerfile: Dockerfile
platforms:
- $TARGET_PLATFORM
image: $IMAGE_NAME:latest
container_name: dms-compliance-tool
ports:
- "$PRIMARY_PORT:$PRIMARY_PORT"
environment:
- PYTHONPATH=/app
- TZ=Asia/Shanghai
volumes:
- ./uploads:/app/uploads
- ./logs:/app/logs
- ./reports:/app/reports
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:$PRIMARY_PORT/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
volumes:
uploads:
logs:
reports:
EOF
fi
# 创建环境配置文件
cat > "$EXPORT_DIR/.env" << EOF
# DMS合规性测试工具环境配置
COMPOSE_PROJECT_NAME=dms-compliance
COMPOSE_FILE=docker-compose.yml
# 服务配置
SERVICE_PORTS=$SELECTED_PORTS
SERVICE_ARCH=$SELECTED_SERVICE_ARCH
TARGET_PLATFORM=$TARGET_PLATFORM
# 应用配置
PYTHONPATH=/app
TZ=Asia/Shanghai
LOG_LEVEL=INFO
# 构建配置
IMAGE_NAME=$IMAGE_NAME
CONTAINER_NAME=dms-compliance-tool
EOF
# 创建启动脚本
echo "[步骤 5/6] 创建管理脚本..."
cat > "$EXPORT_DIR/start.sh" << 'EOF'
#!/bin/bash
# DMS合规性测试工具启动脚本
set -e
echo "=== DMS合规性测试工具启动脚本 ==="
# 检查Docker和Docker Compose
if ! command -v docker &> /dev/null; then
echo "[错误] Docker未安装"
exit 1
fi
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
echo "[错误] Docker Compose未安装"
exit 1
fi
# 选择Docker Compose命令
if docker compose version &> /dev/null; then
COMPOSE_CMD="docker compose"
else
COMPOSE_CMD="docker-compose"
fi
echo "[信息] 使用 $COMPOSE_CMD"
# 创建必要的目录
mkdir -p uploads logs test_reports config
# 构建并启动服务
echo "[信息] 构建Docker镜像..."
$COMPOSE_CMD build
echo "[信息] 启动服务..."
$COMPOSE_CMD up -d
# 等待服务启动
echo "[信息] 等待服务启动..."
sleep 10
# 检查服务状态
if $COMPOSE_CMD ps | grep -q "Up"; then
echo "[成功] 服务启动成功!"
echo ""
echo "访问地址:"
if grep -q "dual" .env; then
echo " API服务器: http://localhost:5050"
echo " 历史查看器: http://localhost:5051"
elif grep -q "fastapi" .env; then
echo " Web界面: http://localhost:5051"
echo " API文档: http://localhost:5051/docs"
echo " ReDoc: http://localhost:5051/redoc"
else
echo " Web界面: http://localhost:5050"
fi
echo ""
echo "管理命令:"
echo " 查看日志: $COMPOSE_CMD logs -f"
echo " 停止服务: $COMPOSE_CMD down"
echo " 重启服务: $COMPOSE_CMD restart"
echo " 查看状态: $COMPOSE_CMD ps"
else
echo "[错误] 服务启动失败"
echo "查看日志: $COMPOSE_CMD logs"
exit 1
fi
EOF
cat > "$EXPORT_DIR/stop.sh" << 'EOF'
#!/bin/bash
# DMS合规性测试工具停止脚本
set -e
echo "=== DMS合规性测试工具停止脚本 ==="
# 选择Docker Compose命令
if docker compose version &> /dev/null; then
COMPOSE_CMD="docker compose"
else
COMPOSE_CMD="docker-compose"
fi
echo "[信息] 停止服务..."
$COMPOSE_CMD down
echo "[成功] 服务已停止"
EOF
cat > "$EXPORT_DIR/logs.sh" << 'EOF'
#!/bin/bash
# DMS合规性测试工具日志查看脚本
# 选择Docker Compose命令
if docker compose version &> /dev/null; then
COMPOSE_CMD="docker compose"
else
COMPOSE_CMD="docker-compose"
fi
echo "=== DMS合规性测试工具日志 ==="
$COMPOSE_CMD logs -f
EOF
# 设置脚本执行权限
chmod +x "$EXPORT_DIR/start.sh"
chmod +x "$EXPORT_DIR/stop.sh"
chmod +x "$EXPORT_DIR/logs.sh"
# 构建Docker镜像
echo "[步骤 6/6] 构建Docker镜像..."
# 保存当前目录
ORIGINAL_DIR=$(pwd)
cd "$EXPORT_DIR"
if [[ "$MULTI_PLATFORM" == "true" ]]; then
echo "[信息] 多平台构建: $TARGET_PLATFORM"
# 创建并使用buildx构建器
docker buildx create --name multiplatform-builder --use 2>/dev/null || docker buildx use multiplatform-builder
# 多平台构建
docker buildx build \
--platform "$TARGET_PLATFORM" \
--tag "$IMAGE_NAME:latest" \
--load \
.
else
echo "[信息] 单平台构建: $TARGET_PLATFORM"
# 单平台构建
docker buildx build \
--platform "$TARGET_PLATFORM" \
--tag "$IMAGE_NAME:latest" \
--load \
.
fi
# 返回原始目录
cd "$ORIGINAL_DIR"
# 导出Docker镜像
echo "[信息] 导出Docker镜像..."
cd "$EXPORT_DIR"
docker save "$IMAGE_NAME:latest" | gzip > "docker-image.tar.gz"
cd "$ORIGINAL_DIR"
# 创建README文件
cat > "$EXPORT_DIR/README.md" << EOF
# DMS合规性测试工具 - Docker Compose部署包
## 系统信息
- **架构**: $(get_service_arch_name "$SELECTED_SERVICE_ARCH")
- **端口**: $SELECTED_PORTS
- **目标平台**: $TARGET_PLATFORM_NAME
- **构建时间**: $(date '+%Y-%m-%d %H:%M:%S')
## 快速开始
### 1. 解压部署包
\`\`\`bash
tar -xzf $ARCHIVE_NAME
cd $EXPORT_DIR
\`\`\`
### 2. 启动服务
\`\`\`bash
./start.sh
\`\`\`
### 3. 访问服务
EOF
if [[ "$SELECTED_SERVICE_ARCH" == "dual" ]]; then
cat >> "$EXPORT_DIR/README.md" << EOF
- API服务器: http://localhost:5050
- 历史查看器: http://localhost:5051
EOF
elif [[ "$SELECTED_SERVICE_ARCH" == "fastapi" ]]; then
cat >> "$EXPORT_DIR/README.md" << EOF
- Web界面: http://localhost:5051
- API文档: http://localhost:5051/docs
- ReDoc文档: http://localhost:5051/redoc
EOF
else
cat >> "$EXPORT_DIR/README.md" << EOF
- Web界面: http://localhost:5050
EOF
fi
cat >> "$EXPORT_DIR/README.md" << EOF
## 管理命令
\`\`\`bash
# 启动服务
./start.sh
# 停止服务
./stop.sh
# 查看日志
./logs.sh
# 查看服务状态
docker-compose ps
# 重启服务
docker-compose restart
\`\`\`
## 目录结构
\`\`\`
$EXPORT_DIR/
├── docker-compose.yml # Docker Compose配置
├── Dockerfile # Docker镜像构建文件
├── .env # 环境变量配置
├── start.sh # 启动脚本
├── stop.sh # 停止脚本
├── logs.sh # 日志查看脚本
├── docker-image.tar.gz # Docker镜像文件
├── ddms_compliance_suite/ # 测试套件源码
├── custom_stages/ # 自定义测试阶段
├── uploads/ # 上传文件目录
├── logs/ # 日志文件目录
├── reports/ # 测试报告目录
└── README.md # 说明文档
\`\`\`
## 系统要求
- Docker 19.03+
- Docker Compose 1.25+ 或 Docker Compose V2
- 支持的平台: $TARGET_PLATFORM_NAME
## 故障排除
### 端口冲突
如果端口被占用,可以修改\`docker-compose.yml\`文件中的端口映射,然后重启服务。
### 镜像加载
如果需要在其他机器上部署,可以使用以下命令加载镜像:
\`\`\`bash
docker load < docker-image.tar.gz
\`\`\`
### 查看详细日志
\`\`\`bash
docker-compose logs -f dms-compliance
\`\`\`
### 重新构建
\`\`\`bash
docker-compose build --no-cache
docker-compose up -d
\`\`\`
## 技术支持
如有问题,请查看日志文件或联系技术支持。
EOF
# 创建压缩包
echo "[信息] 创建压缩包..."
tar -czf "$ARCHIVE_NAME" "$EXPORT_DIR"
# 清理临时目录
rm -rf "$EXPORT_DIR"
# 显示结果
echo ""
echo "=== 构建完成 ==="
echo "[成功] Docker Compose部署包已创建: $ARCHIVE_NAME"
echo "[信息] 架构: $(get_service_arch_name "$SELECTED_SERVICE_ARCH")"
echo "[信息] 端口: $SELECTED_PORTS"
echo "[信息] 平台: $TARGET_PLATFORM_NAME"
echo "[信息] 文件大小: $(du -h "$ARCHIVE_NAME" | cut -f1)"
echo ""
echo "部署说明:"
echo "1. 将 $ARCHIVE_NAME 传输到目标服务器"
echo "2. 解压: tar -xzf $ARCHIVE_NAME"
echo "3. 进入目录: cd $EXPORT_DIR"
echo "4. 启动服务: ./start.sh"
echo ""
if [[ "$SELECTED_SERVICE_ARCH" == "dual" ]]; then
echo "访问地址:"
echo "- API服务器: http://localhost:5050"
echo "- 历史查看器: http://localhost:5051"
elif [[ "$SELECTED_SERVICE_ARCH" == "fastapi" ]]; then
echo "访问地址:"
echo "- Web界面: http://localhost:5051"
echo "- API文档: http://localhost:5051/docs"
echo "- ReDoc: http://localhost:5051/redoc"
else
echo "访问地址: http://localhost:5050"
fi
echo ""
echo "构建完成!"