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

335
docker/export-images.sh Executable file
View File

@@ -0,0 +1,335 @@
#!/bin/bash
# Docker镜像导出脚本
# 用于将构建好的Docker镜像导出为tar文件便于离线部署
set -e
# 脚本配置
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
EXPORT_DIR="${PROJECT_ROOT}/docker-images"
LOG_FILE="${EXPORT_DIR}/export.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 显示此帮助信息
-o, --output DIR 指定导出目录 (默认: ./docker-images)
-c, --compress 启用压缩 (使用gzip)
-v, --verify 导出后验证镜像完整性
--custom-images 导出自定义镜像列表 (用逗号分隔)
--skip-build 跳过镜像构建,直接导出现有镜像
示例:
$0 # 导出所有镜像
$0 -c -v # 导出并压缩,验证完整性
$0 -o /tmp/images --compress # 导出到指定目录并压缩
$0 --custom-images "mysql:8.0,redis:7-alpine" # 导出指定镜像
EOF
}
# 默认配置
COMPRESS=false
VERIFY=false
SKIP_BUILD=false
CUSTOM_IMAGES=""
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-o|--output)
EXPORT_DIR="$2"
shift 2
;;
-c|--compress)
COMPRESS=true
shift
;;
-v|--verify)
VERIFY=true
shift
;;
--custom-images)
CUSTOM_IMAGES="$2"
shift 2
;;
--skip-build)
SKIP_BUILD=true
shift
;;
*)
log_error "未知参数: $1"
show_help
exit 1
;;
esac
done
# 创建导出目录
mkdir -p "$EXPORT_DIR"
mkdir -p "$(dirname "$LOG_FILE")"
log "开始Docker镜像导出过程..."
log "导出目录: $EXPORT_DIR"
# 检查Docker是否运行
if ! docker info >/dev/null 2>&1; then
log_error "Docker未运行或无法访问"
exit 1
fi
# 定义需要导出的镜像列表
if [[ -n "$CUSTOM_IMAGES" ]]; then
IFS=',' read -ra IMAGES <<< "$CUSTOM_IMAGES"
else
IMAGES=(
"knowledge-base-app:latest"
"mysql:8.0"
"redis:7-alpine"
"getmeili/meilisearch:v1.5"
)
fi
# 构建自定义镜像(如果需要)
if [[ "$SKIP_BUILD" == false ]]; then
log "检查是否需要构建自定义镜像..."
# 检查knowledge-base-app镜像是否存在
if ! docker image inspect knowledge-base-app:latest >/dev/null 2>&1; then
log "构建knowledge-base-app镜像..."
cd "$PROJECT_ROOT"
if docker build --platform linux/amd64 -t knowledge-base-app:latest -f Dockerfile --target production .; then
log_success "knowledge-base-app镜像构建成功"
else
log_error "knowledge-base-app镜像构建失败"
exit 1
fi
else
log_success "knowledge-base-app镜像已存在"
fi
fi
# 拉取外部镜像
log "拉取外部镜像..."
for image in "mysql:8.0" "redis:7-alpine" "getmeili/meilisearch:v1.5"; do
if [[ " ${IMAGES[@]} " =~ " ${image} " ]]; then
log "拉取镜像: $image"
if docker pull --platform linux/amd64 "$image"; then
log_success "镜像 $image 拉取成功"
else
log_warning "镜像 $image 拉取失败,将尝试使用本地镜像"
fi
fi
done
# 导出镜像
log "开始导出镜像..."
EXPORTED_FILES=()
for image in "${IMAGES[@]}"; do
log "导出镜像: $image"
# 检查镜像是否存在
if ! docker image inspect "$image" >/dev/null 2>&1; then
log_error "镜像 $image 不存在,跳过导出"
continue
fi
# 生成文件名(替换特殊字符)
filename=$(echo "$image" | sed 's/[\/:]/_/g')
output_file="${EXPORT_DIR}/${filename}.tar"
# 导出镜像
if docker save -o "$output_file" "$image"; then
log_success "镜像 $image 导出成功: $output_file"
EXPORTED_FILES+=("$output_file")
# 显示文件大小
size=$(du -h "$output_file" | cut -f1)
log "文件大小: $size"
else
log_error "镜像 $image 导出失败"
continue
fi
# 压缩文件(如果启用)
if [[ "$COMPRESS" == true ]]; then
log "压缩文件: $output_file"
if gzip "$output_file"; then
compressed_file="${output_file}.gz"
log_success "文件压缩成功: $compressed_file"
# 更新文件列表
EXPORTED_FILES=("${EXPORTED_FILES[@]/$output_file}")
EXPORTED_FILES+=("$compressed_file")
# 显示压缩后大小
compressed_size=$(du -h "$compressed_file" | cut -f1)
original_size=$(du -h "$output_file" 2>/dev/null | cut -f1 || echo "N/A")
log "压缩后大小: $compressed_size (原始: $original_size)"
else
log_error "文件压缩失败"
fi
fi
done
# 验证导出的镜像(如果启用)
if [[ "$VERIFY" == true ]]; then
log "验证导出的镜像..."
for file in "${EXPORTED_FILES[@]}"; do
if [[ -f "$file" ]]; then
log "验证文件: $file"
# 检查文件完整性
if [[ "$file" == *.gz ]]; then
# 验证gzip文件
if gzip -t "$file"; then
log_success "压缩文件完整性验证通过"
else
log_error "压缩文件完整性验证失败"
fi
else
# 验证tar文件
if tar -tf "$file" >/dev/null 2>&1; then
log_success "tar文件完整性验证通过"
else
log_error "tar文件完整性验证失败"
fi
fi
fi
done
fi
# 生成镜像清单
manifest_file="${EXPORT_DIR}/images-manifest.txt"
log "生成镜像清单: $manifest_file"
cat > "$manifest_file" << EOF
# Docker镜像导出清单
# 生成时间: $(date)
# 导出目录: $EXPORT_DIR
# 压缩: $COMPRESS
# 验证: $VERIFY
EOF
for file in "${EXPORTED_FILES[@]}"; do
if [[ -f "$file" ]]; then
filename=$(basename "$file")
size=$(du -h "$file" | cut -f1)
checksum=$(sha256sum "$file" | cut -d' ' -f1)
echo "文件: $filename" >> "$manifest_file"
echo "大小: $size" >> "$manifest_file"
echo "SHA256: $checksum" >> "$manifest_file"
echo "" >> "$manifest_file"
fi
done
# 生成导入脚本
import_script="${EXPORT_DIR}/import-images.sh"
log "生成导入脚本: $import_script"
cat > "$import_script" << 'EOF'
#!/bin/bash
# Docker镜像导入脚本
# 自动生成用于导入导出的Docker镜像
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "开始导入Docker镜像..."
# 检查Docker是否运行
if ! docker info >/dev/null 2>&1; then
echo "错误: Docker未运行或无法访问"
exit 1
fi
# 导入所有tar文件
for file in "$SCRIPT_DIR"/*.tar*; do
if [[ -f "$file" ]]; then
echo "导入镜像: $(basename "$file")"
if [[ "$file" == *.gz ]]; then
# 解压并导入
if gunzip -c "$file" | docker load; then
echo "✓ 镜像导入成功"
else
echo "✗ 镜像导入失败"
fi
else
# 直接导入
if docker load -i "$file"; then
echo "✓ 镜像导入成功"
else
echo "✗ 镜像导入失败"
fi
fi
fi
done
echo "镜像导入完成"
echo "可用镜像列表:"
docker images
EOF
chmod +x "$import_script"
# 显示总结
log_success "镜像导出完成!"
log "导出的文件:"
for file in "${EXPORTED_FILES[@]}"; do
if [[ -f "$file" ]]; then
size=$(du -h "$file" | cut -f1)
log " - $(basename "$file") ($size)"
fi
done
log "生成的文件:"
log " - images-manifest.txt (镜像清单)"
log " - import-images.sh (导入脚本)"
total_size=$(du -sh "$EXPORT_DIR" | cut -f1)
log "总大小: $total_size"
log_success "所有文件已保存到: $EXPORT_DIR"