feat: 新增 Docker 部署支持、Swoole/Octane 集成及相关优化

- 添加 Dockerfile 与多套 docker-compose 配置(开发/生产环境)
- 集成 Laravel Octane (Swoole) 提升性能
- 新增健康检查、监控脚本及部署文档
- 新增 Docker 镜像离线导入包(MySQL/Redis/Meilisearch)
- 优化文档转换、预览服务及队列任务
- 添加 CreateAdminUser 命令与路由健康检查接口
- 新增 Swoole 队列兼容性测试套件

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 15:51:19 +08:00
parent acf549c43c
commit 3c206e9e06
90 changed files with 12731 additions and 1255 deletions

496
docker/import-and-verify.sh Executable file
View File

@@ -0,0 +1,496 @@
#!/bin/bash
# Docker镜像导入和验证脚本
# 用于导入Docker镜像并验证其完整性和兼容性
set -e
# 脚本配置
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_IMAGES_DIR="$(dirname "$SCRIPT_DIR")/docker-images"
LOG_FILE="${DEFAULT_IMAGES_DIR}/import-verify.log"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log() {
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
}
log_success() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] ✓${NC} $1" | tee -a "$LOG_FILE"
}
log_warning() {
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] ⚠${NC} $1" | tee -a "$LOG_FILE"
}
log_error() {
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ✗${NC} $1" | tee -a "$LOG_FILE"
}
# 显示帮助信息
show_help() {
cat << EOF
Docker镜像导入和验证脚本
用法: $0 [选项] [镜像目录]
选项:
-h, --help 显示此帮助信息
-v, --verify-only 仅验证,不导入
-f, --force 强制导入,覆盖现有镜像
-c, --check-manifest 检查清单文件
--skip-compatibility 跳过兼容性检查
--parallel N 并行导入数量 (默认: 2)
--test-run 导入后运行测试容器
参数:
镜像目录 包含Docker镜像文件的目录 (默认: ./docker-images)
示例:
$0 # 导入默认目录中的所有镜像
$0 -v /path/to/images # 仅验证镜像文件
$0 -f --test-run # 强制导入并测试
$0 --check-manifest # 检查清单文件完整性
EOF
}
# 默认配置
VERIFY_ONLY=false
FORCE_IMPORT=false
CHECK_MANIFEST=false
SKIP_COMPATIBILITY=false
PARALLEL_JOBS=2
TEST_RUN=false
IMAGES_DIR="$DEFAULT_IMAGES_DIR"
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-v|--verify-only)
VERIFY_ONLY=true
shift
;;
-f|--force)
FORCE_IMPORT=true
shift
;;
-c|--check-manifest)
CHECK_MANIFEST=true
shift
;;
--skip-compatibility)
SKIP_COMPATIBILITY=true
shift
;;
--parallel)
PARALLEL_JOBS="$2"
if [[ ! "$PARALLEL_JOBS" =~ ^[1-9][0-9]*$ ]]; then
log_error "并行任务数必须是正整数"
exit 1
fi
shift 2
;;
--test-run)
TEST_RUN=true
shift
;;
-*)
log_error "未知参数: $1"
show_help
exit 1
;;
*)
IMAGES_DIR="$1"
shift
;;
esac
done
# 检查输入目录
if [[ ! -d "$IMAGES_DIR" ]]; then
log_error "镜像目录不存在: $IMAGES_DIR"
exit 1
fi
# 创建日志目录
mkdir -p "$(dirname "$LOG_FILE")"
log "开始Docker镜像导入和验证..."
log "镜像目录: $IMAGES_DIR"
log "仅验证: $VERIFY_ONLY"
log "强制导入: $FORCE_IMPORT"
log "并行任务: $PARALLEL_JOBS"
# 检查Docker是否运行
check_docker() {
log "检查Docker环境..."
if ! command -v docker >/dev/null 2>&1; then
log_error "Docker未安装"
exit 1
fi
if ! docker info >/dev/null 2>&1; then
log_error "Docker未运行或无法访问"
exit 1
fi
local docker_version=$(docker --version)
log_success "Docker环境正常: $docker_version"
# 检查系统架构
local system_arch=$(uname -m)
log "系统架构: $system_arch"
if [[ "$system_arch" != "x86_64" ]] && [[ "$SKIP_COMPATIBILITY" == false ]]; then
log_warning "系统架构不是x86_64可能存在兼容性问题"
fi
}
# 检查清单文件
check_manifest_file() {
if [[ "$CHECK_MANIFEST" == false ]]; then
return 0
fi
local manifest_file="${IMAGES_DIR}/images-manifest.txt"
log "检查清单文件..."
if [[ ! -f "$manifest_file" ]]; then
log_warning "清单文件不存在: $manifest_file"
return 1
fi
log "验证清单文件中的镜像..."
# 解析清单文件
local current_file=""
local expected_checksum=""
local verification_failed=0
while IFS= read -r line; do
if [[ "$line" =~ ^文件:\ (.+)$ ]]; then
current_file="${BASH_REMATCH[1]}"
elif [[ "$line" =~ ^SHA256:\ (.+)$ ]]; then
expected_checksum="${BASH_REMATCH[1]}"
if [[ -n "$current_file" ]] && [[ -n "$expected_checksum" ]]; then
local file_path="${IMAGES_DIR}/${current_file}"
if [[ -f "$file_path" ]]; then
log "验证文件: $current_file"
local actual_checksum=$(sha256sum "$file_path" | cut -d' ' -f1)
if [[ "$actual_checksum" == "$expected_checksum" ]]; then
log_success "校验和匹配: $current_file"
else
log_error "校验和不匹配: $current_file"
log_error "期望: $expected_checksum"
log_error "实际: $actual_checksum"
((verification_failed++))
fi
else
log_error "文件不存在: $current_file"
((verification_failed++))
fi
current_file=""
expected_checksum=""
fi
fi
done < "$manifest_file"
if [[ $verification_failed -eq 0 ]]; then
log_success "清单文件验证通过"
return 0
else
log_error "清单文件验证失败,$verification_failed 个文件有问题"
return 1
fi
}
# 验证镜像文件
verify_image_file() {
local file="$1"
local filename=$(basename "$file")
log "验证镜像文件: $filename"
# 检查文件是否存在
if [[ ! -f "$file" ]]; then
log_error "文件不存在: $filename"
return 1
fi
# 检查文件大小
local file_size=$(du -h "$file" | cut -f1)
log "文件大小: $file_size"
# 验证文件完整性
if [[ "$file" == *.tar.gz ]]; then
# 验证gzip文件
if gzip -t "$file" 2>/dev/null; then
log_success "压缩文件完整性验证通过: $filename"
else
log_error "压缩文件完整性验证失败: $filename"
return 1
fi
elif [[ "$file" == *.tar ]]; then
# 验证tar文件
if tar -tf "$file" >/dev/null 2>&1; then
log_success "tar文件完整性验证通过: $filename"
else
log_error "tar文件完整性验证失败: $filename"
return 1
fi
else
log_warning "未知文件类型,跳过验证: $filename"
return 1
fi
return 0
}
# 导入镜像文件
import_image_file() {
local file="$1"
local filename=$(basename "$file")
log "导入镜像文件: $filename"
# 首先验证文件
if ! verify_image_file "$file"; then
log_error "文件验证失败,跳过导入: $filename"
return 1
fi
# 检查是否需要强制导入
local import_args=""
if [[ "$FORCE_IMPORT" == true ]]; then
import_args="--quiet"
fi
# 导入镜像
local import_output
if [[ "$file" == *.tar.gz ]]; then
# 解压并导入
import_output=$(gunzip -c "$file" | docker load 2>&1)
else
# 直接导入
import_output=$(docker load -i "$file" 2>&1)
fi
if [[ $? -eq 0 ]]; then
log_success "镜像导入成功: $filename"
# 提取导入的镜像名称
local imported_image=$(echo "$import_output" | grep "Loaded image" | sed 's/Loaded image: //')
if [[ -n "$imported_image" ]]; then
log "导入的镜像: $imported_image"
# 验证镜像架构
if [[ "$SKIP_COMPATIBILITY" == false ]]; then
verify_image_architecture "$imported_image"
fi
fi
return 0
else
log_error "镜像导入失败: $filename"
log_error "错误信息: $import_output"
return 1
fi
}
# 验证镜像架构
verify_image_architecture() {
local image="$1"
log "验证镜像架构: $image"
# 获取镜像信息
local image_info=$(docker image inspect "$image" 2>/dev/null)
if [[ $? -ne 0 ]]; then
log_error "无法获取镜像信息: $image"
return 1
fi
# 提取架构信息
local architecture=$(echo "$image_info" | grep -o '"Architecture":"[^"]*"' | cut -d'"' -f4)
local os=$(echo "$image_info" | grep -o '"Os":"[^"]*"' | cut -d'"' -f4)
log "镜像架构: $os/$architecture"
# 检查架构兼容性
local system_arch=$(uname -m)
local expected_arch="amd64"
if [[ "$system_arch" == "x86_64" ]]; then
expected_arch="amd64"
elif [[ "$system_arch" == "aarch64" ]]; then
expected_arch="arm64"
fi
if [[ "$architecture" == "$expected_arch" ]]; then
log_success "镜像架构兼容: $architecture"
return 0
else
log_warning "镜像架构可能不兼容: $architecture (系统: $system_arch)"
return 1
fi
}
# 测试镜像运行
test_image_run() {
local image="$1"
log "测试镜像运行: $image"
# 根据镜像类型选择测试命令
local test_command=""
local container_name="test-$(echo "$image" | sed 's/[\/:]/_/g')-$$"
case "$image" in
*mysql*)
test_command="docker run --rm --name $container_name -e MYSQL_ROOT_PASSWORD=test -d $image"
;;
*redis*)
test_command="docker run --rm --name $container_name -d $image"
;;
*meilisearch*)
test_command="docker run --rm --name $container_name -e MEILI_MASTER_KEY=test -d $image"
;;
*knowledge-base-app*)
# 应用镜像需要更复杂的测试
log_warning "应用镜像测试需要完整环境,跳过单独测试"
return 0
;;
*)
log_warning "未知镜像类型,跳过运行测试: $image"
return 0
;;
esac
# 运行测试容器
if eval "$test_command"; then
log "测试容器启动成功: $container_name"
# 等待容器启动
sleep 5
# 检查容器状态
if docker ps | grep -q "$container_name"; then
log_success "镜像运行测试通过: $image"
# 停止测试容器
docker stop "$container_name" >/dev/null 2>&1 || true
return 0
else
log_error "测试容器启动失败: $image"
# 显示容器日志
docker logs "$container_name" 2>/dev/null || true
docker rm "$container_name" >/dev/null 2>&1 || true
return 1
fi
else
log_error "无法启动测试容器: $image"
return 1
fi
}
# 处理单个镜像文件
process_image_file() {
local file="$1"
local filename=$(basename "$file")
log "处理镜像文件: $filename"
if [[ "$VERIFY_ONLY" == true ]]; then
verify_image_file "$file"
return $?
else
if import_image_file "$file"; then
# 如果需要测试运行
if [[ "$TEST_RUN" == true ]]; then
# 提取镜像名称进行测试
local image_name=$(echo "$filename" | sed 's/\.tar.*$//' | sed 's/_/:/g')
test_image_run "$image_name" || true
fi
return 0
else
return 1
fi
fi
}
# 主处理流程
main() {
check_docker
check_manifest_file
# 查找镜像文件
local image_files=($(find "$IMAGES_DIR" -name "*.tar*" -type f))
if [[ ${#image_files[@]} -eq 0 ]]; then
log_error "$IMAGES_DIR 中没有找到镜像文件"
exit 1
fi
log "找到 ${#image_files[@]} 个镜像文件"
# 处理镜像文件
local processed=0
local failed=0
local total=${#image_files[@]}
# 使用并行处理
export -f process_image_file verify_image_file import_image_file verify_image_architecture test_image_run
export -f log log_success log_warning log_error
export VERIFY_ONLY FORCE_IMPORT SKIP_COMPATIBILITY TEST_RUN LOG_FILE
export RED GREEN YELLOW BLUE NC
for file in "${image_files[@]}"; do
if process_image_file "$file"; then
((processed++))
else
((failed++))
fi
done
# 显示导入的镜像
if [[ "$VERIFY_ONLY" == false ]]; then
log "当前Docker镜像列表:"
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
fi
# 显示总结
log_success "处理完成!"
log "总文件数: $total"
log "成功处理: $processed"
log "失败数量: $failed"
if [[ $failed -gt 0 ]]; then
log_warning "$failed 个文件处理失败,请检查日志"
exit 1
else
log_success "所有文件处理成功"
fi
}
# 执行主流程
main