#!/bin/bash # ECShopX 开发环境设置脚本(Docker 方式) # 使用单容器运行所有服务:PHP-FPM、Nginx、MySQL、Redis # 支持三个项目:ECShopX、ECShopX_admin-frontend、ECShopX_mobile-frontend set -e # =========================================== # 配置变量(可根据需要修改) # =========================================== # 脚本目录和项目根目录 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$SCRIPT_DIR" PARENT_DIR="$(cd "$PROJECT_ROOT/.." && pwd)" # 容器配置 CONTAINER_NAME="ecshopx-dev" DOCKER_COMPOSE_FILE="$PROJECT_ROOT/docker-compose.dev.yml" # 数据库配置 MYSQL_ROOT_PWD="rootpassword" MYSQL_DATABASE="ecshopx" MYSQL_USER="ecshopx" MYSQL_PASSWORD="ecshopx" # Redis 配置 REDIS_PASSWORD="redispassword" # 默认选项 REBUILD=false SKIP_ADMIN=false SKIP_VSHOP=false SKIP_PC=false # 业务模式(全局变量,在 build_admin 中设置,build_vshop 中复用) SELECTED_PLATFORM="" # 默认语言(全局变量,在第一个编译的项目中设置,后续项目复用) SELECTED_LANG="" # 语言/业务模式等交互必须从 /dev/tty 读取:若仅从 stdin 读,在「管道、重定向、部分 IDE 集成终端」下会立即 EOF, # 空变量会触发 ${VAR:-1},表现为未确认就选用默认项 1。 # 跟踪已安装的前端项目 INSTALLED_ADMIN=false INSTALLED_VSHOP=false INSTALLED_PC=false # 记录开始时间 START_TIME=$(date +%s) # 开源安装统计(可选):安装成功时上报,需同时配置 OPEN_SOURCE_STAT_GATEWAY(网关根 URL,无尾部斜杠) # 与 OPEN_SOURCE_STAT_SECRET_ECHOPX(验签密钥,勿提交到仓库) # Docker Compose 命令(兼容新旧版本) DOCKER_COMPOSE_CMD="" # =========================================== # 颜色和日志函数 # =========================================== RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_step() { echo -e "${CYAN}[STEP]${NC} $1" } # =========================================== # 进度条和动画函数 # =========================================== # Spinner 动画(用于不确定时长的操作) spinner() { local pid=$1 local message=${2:-"处理中"} local spinstr='|/-\' local delay=0.1 while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} $message ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep $delay done printf "\r${CYAN}[INFO]${NC} $message 完成\n" } # 简单的进度条(用于确定时长的操作) progress_bar() { local current=$1 local total=$2 local width=50 local percentage=$((current * 100 / total)) local completed=$((current * width / total)) local remaining=$((width - completed)) printf "\r${CYAN}[INFO]${NC} 进度: [" printf "%${completed}s" | tr ' ' '=' printf "%${remaining}s" | tr ' ' '-' printf "] %d%% (%d/%d)" $percentage $current $total } # 带消息的进度条 progress_bar_with_message() { local current=$1 local total=$2 local message=$3 local width=40 local percentage=$((current * 100 / total)) local completed=$((current * width / total)) local remaining=$((width - completed)) printf "\r${CYAN}[INFO]${NC} $message [" printf "%${completed}s" | tr ' ' '=' printf "%${remaining}s" | tr ' ' '-' printf "] %d%%" $percentage } # 等待操作完成并显示进度 wait_with_progress() { local command=$1 local message=${2:-"执行中"} local max_wait=${3:-60} local interval=${4:-1} local count=0 # 后台执行命令 eval "$command" > /tmp/progress_output.log 2>&1 & local pid=$! # 显示进度 while kill -0 $pid 2>/dev/null && [ $count -lt $max_wait ]; do progress_bar_with_message $count $max_wait "$message" sleep $interval count=$((count + interval)) done # 等待命令完成 wait $pid local exit_code=$? # 清除进度条 printf "\r${GREEN}[SUCCESS]${NC} $message 完成" printf "%$((${#message} + 20))s" "" echo "" return $exit_code } # =========================================== # 检测 Docker Compose 命令 # =========================================== detect_docker_compose() { # 优先使用 docker compose (v2) if docker compose version &>/dev/null 2>&1; then DOCKER_COMPOSE_CMD="docker compose" log_info "使用 Docker Compose V2 (docker compose)" # 回退到 docker-compose (v1) elif command -v docker-compose &>/dev/null; then DOCKER_COMPOSE_CMD="docker-compose" log_info "使用 Docker Compose V1 (docker-compose)" else log_error "未找到 Docker Compose 命令" exit 1 fi } # =========================================== # 检查容器是否运行 # =========================================== is_container_running() { docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$" 2>/dev/null } # =========================================== # 检查容器是否存在(包括已停止的) # =========================================== container_exists() { docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$" 2>/dev/null } # =========================================== # 帮助信息 # =========================================== show_help() { echo "ECShopX 开发环境设置脚本" echo "" echo "用法: $0 [选项]" echo "" echo "选项:" echo " --help, -h 显示帮助信息" echo " --rebuild 强制重新构建镜像(不使用缓存)" echo " --skip-admin 跳过 ECShopX_admin-frontend 编译" echo " --skip-vshop 跳过 ECShopX_mobile-frontend 编译" echo "" echo "示例:" echo " $0 # 正常启动(使用缓存)" echo " $0 --rebuild # 重新构建镜像" echo " $0 --skip-admin # 跳过管理后台编译" echo "" exit 0 } # =========================================== # 解析命令行参数 # =========================================== parse_args() { while [[ $# -gt 0 ]]; do case $1 in --help|-h) show_help ;; --rebuild) REBUILD=true shift ;; --skip-admin) SKIP_ADMIN=true shift ;; --skip-vshop) SKIP_VSHOP=true shift ;; --skip-pc) SKIP_PC=true shift ;; *) log_error "未知参数: $1" echo "使用 --help 查看帮助信息" exit 1 ;; esac done } # =========================================== # 检测操作系统 # =========================================== detect_os() { if [[ "$OSTYPE" == "darwin"* ]]; then OS="macos" elif [[ "$OSTYPE" == "linux-gnu"* ]]; then OS="linux" else log_error "不支持的操作系统: $OSTYPE" exit 1 fi log_info "检测到操作系统: $OS" } # =========================================== # 检测 Docker # =========================================== check_docker() { log_info "检测 Docker 安装状态..." if ! command -v docker &> /dev/null; then log_error "Docker 未安装" log_info "安装方式:" log_info " macOS: 下载 Docker Desktop https://www.docker.com/products/docker-desktop" log_info " Linux: sudo apt-get install docker.io docker-compose (Ubuntu/Debian)" exit 1 fi # 检测 Docker Compose 命令 detect_docker_compose # 检查 Docker 是否正在运行 if ! docker info &> /dev/null 2>&1; then log_warning "Docker 未运行,尝试自动启动..." if [ "$OS" = "macos" ]; then open -a Docker log_info "正在启动 Docker Desktop,请等待..." for i in {1..60}; do if docker info &> /dev/null 2>&1; then log_success "Docker 已启动" break fi sleep 2 if [ $((i % 5)) -eq 0 ]; then log_info " 等待 Docker 启动... ($i/60)" fi done if ! docker info &> /dev/null 2>&1; then log_error "Docker 启动超时,请手动启动 Docker Desktop 后重新运行脚本" exit 1 fi elif [ "$OS" = "linux" ]; then sudo systemctl start docker || { log_error "Docker 启动失败,请手动启动" exit 1 } sleep 3 if ! docker info &> /dev/null 2>&1; then log_error "Docker 启动失败" exit 1 fi log_success "Docker 已启动" fi fi log_success "Docker 和 Docker Compose 已就绪" } # =========================================== # 检查并克隆前端项目 # =========================================== check_and_clone_frontend() { log_step "检查前端项目目录..." # 检查 ECShopX_admin-frontend ADMIN_DIR="$PARENT_DIR/ECShopX_admin-frontend" ADMIN_REPO="https://gitee.com/ShopeX/ECShopX_admin-frontend.git" if [ ! -d "$ADMIN_DIR" ] || [ ! -f "$ADMIN_DIR/package.json" ]; then if [ ! -d "$ADMIN_DIR" ]; then log_warning "ECShopX_admin-frontend 目录不存在" else log_warning "ECShopX_admin-frontend 目录存在但缺少 package.json" fi echo "" echo -n "是否从 Gitee 克隆管理后台(ECShopX_admin-frontend)代码? [Y/n]: " read -r answer < /dev/tty if [ -z "$answer" ] || [ "$answer" = "Y" ] || [ "$answer" = "y" ] || [ "$answer" = "yes" ] || [ "$answer" = "YES" ]; then if [ -d "$ADMIN_DIR" ]; then log_info "清空现有目录内容..." find "$ADMIN_DIR" -mindepth 1 -delete 2>/dev/null fi log_info "正在从 Gitee 克隆管理后台(ECShopX_admin-frontend)..." git clone "$ADMIN_REPO" "$ADMIN_DIR" > /tmp/git_clone_admin.log 2>&1 & local clone_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $clone_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 克隆管理后台(ECShopX_admin-frontend)中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $clone_pid local clone_exit=$? if [ $clone_exit -eq 0 ]; then printf "\r${GREEN}[SUCCESS]${NC} 管理后台(ECShopX_admin-frontend)克隆成功" printf "%50s" "" echo "" INSTALLED_ADMIN=true else printf "\r${RED}[ERROR]${NC} 管理后台(ECShopX_admin-frontend)克隆失败" printf "%50s" "" echo "" cat /tmp/git_clone_admin.log 2>/dev/null | tail -10 exit 1 fi else log_warning "跳过管理后台(ECShopX_admin-frontend)克隆" fi else log_info "ECShopX_admin-frontend 目录已存在且包含 package.json,跳过克隆" INSTALLED_ADMIN=true fi # 修复语法错误:注释掉重复的 else...fi 块(原代码第401-403行) # else # log_info "ECShopX_admin-frontend 目录已存在且包含 package.json,跳过克隆" # fi # 检查 ECShopX_mobile-frontend VSHOP_DIR="$PARENT_DIR/ECShopX_mobile-frontend" VSHOP_REPO="https://gitee.com/ShopeX/ECShopX_mobile-frontend.git" if [ ! -d "$VSHOP_DIR" ] || [ ! -f "$VSHOP_DIR/package.json" ]; then if [ ! -d "$VSHOP_DIR" ]; then log_warning "ECShopX_mobile-frontend 目录不存在" else log_warning "ECShopX_mobile-frontend 目录存在但缺少 package.json" fi echo "" echo -n "是否从 Gitee 克隆移动商城(ECShopX_mobile-frontend)代码? [Y/n]: " read -r answer < /dev/tty if [ -z "$answer" ] || [ "$answer" = "Y" ] || [ "$answer" = "y" ] || [ "$answer" = "yes" ] || [ "$answer" = "YES" ]; then if [ -d "$VSHOP_DIR" ]; then log_info "清空现有目录内容..." find "$VSHOP_DIR" -mindepth 1 -delete 2>/dev/null fi log_info "正在从 Gitee 克隆移动商城(ECShopX_mobile-frontend)..." git clone "$VSHOP_REPO" "$VSHOP_DIR" > /tmp/git_clone_vshop.log 2>&1 & local clone_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $clone_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 克隆移动商城(ECShopX_mobile-frontend)中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $clone_pid local clone_exit=$? if [ $clone_exit -eq 0 ]; then printf "\r${GREEN}[SUCCESS]${NC}移动商城(ECShopX_mobile-frontend)克隆成功" printf "%50s" "" echo "" INSTALLED_VSHOP=true else printf "\r${RED}[ERROR]${NC}移动商城(ECShopX_mobile-frontend)克隆失败" printf "%50s" "" echo "" cat /tmp/git_clone_vshop.log 2>/dev/null | tail -10 exit 1 fi else log_warning "跳过移动商城(ECShopX_mobile-frontend)克隆" fi else log_info "ECShopX_mobile-frontend 目录已存在且包含 package.json,跳过克隆" INSTALLED_VSHOP=true fi # 检查 ECShopX_desktop-frontend PC_DIR="$PARENT_DIR/ECShopX_desktop-frontend" PC_REPO="https://gitee.com/ShopeX/ECShopX_desktop-frontend.git" if [ ! -d "$PC_DIR" ] || [ ! -f "$PC_DIR/package.json" ]; then if [ ! -d "$PC_DIR" ]; then log_warning "ECShopX_desktop-frontend 目录不存在" else log_warning "ECShopX_desktop-frontend 目录存在但缺少 package.json" fi echo "" echo -n "是否从 Gitee 克隆PC商城(ECShopX_desktop-frontend)代码? [Y/n]: " read -r answer < /dev/tty if [ -z "$answer" ] || [ "$answer" = "Y" ] || [ "$answer" = "y" ] || [ "$answer" = "yes" ] || [ "$answer" = "YES" ]; then if [ -d "$PC_DIR" ]; then log_info "清空现有目录内容..." find "$PC_DIR" -mindepth 1 -delete 2>/dev/null fi log_info "正在从 Gitee 克隆PC商城(ECShopX_desktop-frontend)..." git clone "$PC_REPO" "$PC_DIR" > /tmp/git_clone_pc.log 2>&1 & local clone_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $clone_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 克隆PC商城(ECShopX_desktop-frontend)中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $clone_pid local clone_exit=$? if [ $clone_exit -eq 0 ]; then printf "\r${GREEN}[SUCCESS]${NC}PC商城(ECShopX_desktop-frontend)克隆成功" printf "%50s" "" echo "" INSTALLED_PC=true else printf "\r${RED}[ERROR]${NC}PC商城(ECShopX_desktop-frontend)克隆失败" printf "%50s" "" echo "" cat /tmp/git_clone_pc.log 2>/dev/null | tail -10 exit 1 fi else log_warning "跳过PC商城(ECShopX_desktop-frontend)克隆" fi else log_info "ECShopX_desktop-frontend 目录已存在且包含 package.json,跳过克隆" INSTALLED_PC=true fi echo "" } # =========================================== # 检查容器状态 # =========================================== check_container_status() { # 检查容器是否存在(包括已停止的) if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then # 检查容器是否正在运行 if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then log_warning "容器 $CONTAINER_NAME 正在运行" else log_warning "容器 $CONTAINER_NAME 已存在但未运行" fi echo "" echo -n "请选择操作: [1] 重启容器 [2] 使用现有容器 [3] 退出: " read -r choice < /dev/tty case $choice in 1) log_info "停止并删除现有容器..." $DOCKER_COMPOSE_CMD -f "$DOCKER_COMPOSE_FILE" down return 0 # 需要重新构建 ;; 2) log_info "使用现有容器..." # 如果容器未运行,先启动它 if ! is_container_running; then log_info "启动现有容器..." $DOCKER_COMPOSE_CMD -f "$DOCKER_COMPOSE_FILE" up -d sleep 5 wait_for_services fi return 1 # 跳过构建 ;; 3) log_info "退出脚本" exit 0 ;; *) log_info "使用现有容器..." return 1 ;; esac fi return 0 } # =========================================== # 配置环境文件 # =========================================== configure_env() { log_info "配置环境文件..." cd "$PROJECT_ROOT" if [ ! -f ".env" ]; then for template in ".env.full" ".env.example" ".env.template"; do if [ -f "$template" ]; then log_info "从 $template 复制创建 .env 文件..." cp "$template" .env break fi done fi } # =========================================== # 构建并启动 Docker 容器 # =========================================== run_docker() { log_step "启动 Docker 容器..." cd "$PROJECT_ROOT" if [ ! -f "$DOCKER_COMPOSE_FILE" ]; then log_error "未找到 $DOCKER_COMPOSE_FILE 文件" exit 1 fi # 构建镜像 if [ "$REBUILD" = true ]; then log_info "强制重新构建镜像(--no-cache)..." log_info "这可能需要较长时间,请耐心等待..." $DOCKER_COMPOSE_CMD -f "$DOCKER_COMPOSE_FILE" build --no-cache > /tmp/docker_build.log 2>&1 & local build_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $build_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 构建 Docker 镜像中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.3 done wait $build_pid local build_exit=$? if [ $build_exit -ne 0 ]; then printf "\r${RED}[ERROR]${NC} Docker 镜像构建失败" printf "%50s" "" echo "" cat /tmp/docker_build.log 2>/dev/null | tail -30 exit 1 else printf "\r${GREEN}[SUCCESS]${NC} Docker 镜像构建完成" printf "%50s" "" echo "" fi else log_info "构建镜像(使用缓存)..." $DOCKER_COMPOSE_CMD -f "$DOCKER_COMPOSE_FILE" build > /tmp/docker_build.log 2>&1 & local build_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $build_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 构建 Docker 镜像中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.3 done wait $build_pid local build_exit=$? if [ $build_exit -ne 0 ]; then printf "\r${RED}[ERROR]${NC} Docker 镜像构建失败" printf "%50s" "" echo "" cat /tmp/docker_build.log 2>/dev/null | tail -30 exit 1 else printf "\r${GREEN}[SUCCESS]${NC} Docker 镜像构建完成" printf "%50s" "" echo "" fi fi # 启动容器 log_info "启动容器..." $DOCKER_COMPOSE_CMD -f "$DOCKER_COMPOSE_FILE" up -d || { log_error "Docker 服务启动失败" exit 1 } log_info "等待服务启动..." sleep 5 # 显示容器状态 $DOCKER_COMPOSE_CMD -f "$DOCKER_COMPOSE_FILE" ps # 等待服务就绪 wait_for_services } # =========================================== # 等待服务就绪 # =========================================== wait_for_services() { # 等待 Supervisor log_info "等待 Supervisor 启动..." for i in {1..20}; do if docker exec "$CONTAINER_NAME" supervisorctl status &>/dev/null 2>&1; then break fi sleep 1 done # 等待 MySQL log_info "等待 MySQL 服务就绪..." for i in {1..60}; do if docker exec "$CONTAINER_NAME" mysqladmin ping --socket=/var/run/mysqld/mysqld.sock -u root -p"$MYSQL_ROOT_PWD" --silent 2>/dev/null || \ docker exec "$CONTAINER_NAME" mysqladmin ping -h 127.0.0.1 -u root -p"$MYSQL_ROOT_PWD" --silent 2>/dev/null; then log_success "MySQL 服务已就绪" break fi sleep 2 if [ $((i % 5)) -eq 0 ]; then log_info " 等待 MySQL... ($i/60)" fi if [ $i -eq 60 ]; then log_error "MySQL 服务启动超时" docker exec "$CONTAINER_NAME" tail -n 30 /var/log/supervisor/mysql.log 2>/dev/null || true exit 1 fi done # 等待 Redis log_info "等待 Redis 服务就绪..." for i in {1..30}; do if docker exec "$CONTAINER_NAME" redis-cli -a "$REDIS_PASSWORD" ping 2>/dev/null | grep -q "PONG"; then log_success "Redis 服务已就绪" break fi sleep 2 if [ $((i % 5)) -eq 0 ]; then log_info " 等待 Redis... ($i/30)" fi if [ $i -eq 30 ]; then log_error "Redis 服务启动超时" docker exec "$CONTAINER_NAME" tail -n 30 /var/log/supervisor/redis.log 2>/dev/null || true exit 1 fi done } # =========================================== # 从 ECShopX .env 读取 PRODUCT_MODEL 并设置 SELECTED_PLATFORM # =========================================== read_product_model_from_env() { if [ -n "$SELECTED_PLATFORM" ]; then return 0 fi local product_model="" product_model=$(docker exec "$CONTAINER_NAME" sh -c \ "cd /data/httpd/ECShopX && grep '^PRODUCT_MODEL=' .env 2>/dev/null | cut -d'=' -f2" 2>/dev/null) if [ "$product_model" = "standard" ]; then SELECTED_PLATFORM="standard" log_info "从 ECShopX 配置文件读取业务模式: B2C (PRODUCT_MODEL=standard)" elif [ "$product_model" = "platform" ]; then SELECTED_PLATFORM="platform" log_info "从 ECShopX 配置文件读取业务模式: BBC (PRODUCT_MODEL=platform)" else log_warning "未能从 ECShopX 配置文件读取 PRODUCT_MODEL,将使用默认值 platform" SELECTED_PLATFORM="platform" fi } # =========================================== # 配置 PHP 应用 # =========================================== configure_application() { log_info "==========================================" log_info "开始配置 ECShopX(PHP API)..." log_info "==========================================" # 配置本地 .env 文件 configure_env # 配置容器内 .env 文件 log_info "配置应用环境..." docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX && \ if [ ! -f .env ]; then \ cp .env.example .env 2>/dev/null || cp .env.full .env 2>/dev/null || touch .env; \ fi" || true # 更新数据库配置和存储配置 log_info "更新 .env 配置..." docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX && \ sed -i 's/^DB_HOST=.*/DB_HOST=127.0.0.1/' .env && \ sed -i 's/^DB_PORT=.*/DB_PORT=3306/' .env && \ sed -i 's/^DB_DATABASE=.*/DB_DATABASE=$MYSQL_DATABASE/' .env && \ sed -i 's/^DB_USERNAME=.*/DB_USERNAME=$MYSQL_USER/' .env && \ sed -i 's/^DB_PASSWORD=.*/DB_PASSWORD=$MYSQL_PASSWORD/' .env && \ sed -i 's/^REDIS_HOST=.*/REDIS_HOST=127.0.0.1/' .env && \ sed -i 's/^REDIS_PORT=.*/REDIS_PORT=6379/' .env && \ sed -i 's/^REDIS_PASSWORD=.*/REDIS_PASSWORD=$REDIS_PASSWORD/' .env && \ sed -i 's/^REDIS_DATABASE=.*/REDIS_DATABASE=0/' .env && \ (grep -q '^DISK_DRIVER=' .env && sed -i 's/^DISK_DRIVER=.*/DISK_DRIVER=local/' .env || echo 'DISK_DRIVER=local' >> .env) && \ (grep -q '^APP_URL=' .env && sed -i 's|^APP_URL=.*|APP_URL=http://localhost:8080|' .env || echo 'APP_URL=http://localhost:8080' >> .env)" 2>/dev/null || true # 选择业务模式并写入 PRODUCT_MODEL echo "" log_info "请选择业务模式:" log_info " 1) BBC (多商户入驻电商平台模式)" log_info " 2) B2C (线上商城、O2O云店、内购商城、供应链线上商城等模式)" echo "" while true; do read -r -p "请输入选项 (1 或 2,默认: 1): " BIZ_MODE_CHOICE < /dev/tty BIZ_MODE_CHOICE=${BIZ_MODE_CHOICE:-1} if [ "$BIZ_MODE_CHOICE" = "1" ] || [ "$BIZ_MODE_CHOICE" = "bbc" ]; then SELECTED_PLATFORM="platform" log_info "已选择业务模式: BBC (B2B2C)" break elif [ "$BIZ_MODE_CHOICE" = "2" ] || [ "$BIZ_MODE_CHOICE" = "b2c" ]; then SELECTED_PLATFORM="standard" log_info "已选择业务模式: B2C" break else log_error "无效的选项,请输入 1 或 2" fi done echo "" # 将 PRODUCT_MODEL 写入 ECShopX 的 .env docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX && \ if grep -q '^PRODUCT_MODEL=' .env 2>/dev/null; then \ sed -i 's/^PRODUCT_MODEL=.*/PRODUCT_MODEL=$SELECTED_PLATFORM/' .env; \ else \ echo 'PRODUCT_MODEL=$SELECTED_PLATFORM' >> .env; \ fi" 2>/dev/null || true log_success "已将 PRODUCT_MODEL=$SELECTED_PLATFORM 写入 ECShopX .env" # 安装 Composer 依赖 log_info "检查并安装 Composer 依赖..." # 检查是否需要安装依赖 NEED_INSTALL=$(docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX && \ if [ ! -f composer.phar ]; then \ echo 'ERROR'; \ elif [ ! -d vendor ] || [ ! -f vendor/autoload.php ]; then \ echo 'YES'; \ else \ echo 'NO'; \ fi" 2>/dev/null) if [ "$NEED_INSTALL" = "ERROR" ]; then log_error "composer.phar 文件不存在" exit 1 elif [ "$NEED_INSTALL" = "YES" ]; then log_info "开始安装 Composer 依赖(这可能需要一些时间)..." # 后台执行并显示进度 docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX && \ php -d memory_limit=-1 composer.phar config repo.packagist composer https://mirrors.aliyun.com/composer/ 2>/dev/null || true && \ php -d memory_limit=-1 composer.phar install -o --no-interaction --prefer-dist" > /tmp/composer_output.log 2>&1 & local composer_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $composer_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 安装 Composer 依赖中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $composer_pid local composer_exit=$? if [ $composer_exit -eq 0 ]; then printf "\r${GREEN}[SUCCESS]${NC} Composer 依赖安装完成" printf "%50s" "" echo "" else printf "\r${RED}[ERROR]${NC} Composer 依赖安装失败" printf "%50s" "" echo "" cat /tmp/composer_output.log 2>/dev/null | tail -20 exit 1 fi else log_success "Composer 依赖已存在,跳过安装" fi # 生成应用密钥(仅在不存在时) APP_KEY=$(docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX && grep '^APP_KEY=' .env | cut -d'=' -f2" 2>/dev/null) if [ -z "$APP_KEY" ] || [ "$APP_KEY" = "" ]; then log_info "生成应用密钥..." docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX && php artisan key:generate --force" 2>/dev/null || { log_warning "应用密钥生成失败" } else log_info "应用密钥已存在,跳过生成" fi # 迁移前修正 ECShopX 目录权限(容器内 www-data:www-data) log_info "修正 ECShopX 目录权限为 www-data:www-data..." docker exec "$CONTAINER_NAME" chown -R www-data:www-data /data/httpd/ECShopX 2>/dev/null || { log_warning "修正目录权限失败,继续执行迁移" } # 执行数据库迁移 log_info "执行数据库迁移..." docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX && php artisan doctrine:migrations:migrate --no-interaction" > /tmp/migration_output.log 2>&1 & local migration_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $migration_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 执行数据库迁移中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $migration_pid local migration_exit=$? if [ $migration_exit -ne 0 ]; then printf "\r${RED}[ERROR]${NC} 数据库迁移失败" printf "%50s" "" echo "" cat /tmp/migration_output.log 2>/dev/null | tail -20 log_info "请手动执行: docker exec $CONTAINER_NAME sh -c 'cd /data/httpd/ECShopX && php artisan doctrine:migrations:migrate --no-interaction'" exit 1 else printf "\r${GREEN}[SUCCESS]${NC} 数据库迁移完成" printf "%50s" "" echo "" fi # 创建存储链接(storage:link) log_info "创建存储目录链接..." docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX && \ if [ ! -L public/storage ] && [ ! -d public/storage ]; then \ php artisan storage:link 2>/dev/null || true; \ fi" || { log_warning "存储链接创建失败,将稍后重试" } log_success "存储链接配置完成" # 初始化管理员密码 init_admin_password } # =========================================== # 初始化管理员密码 # =========================================== init_admin_password() { echo "" while true; do echo -n "请输入管理员密码: " read -rs admin_password < /dev/tty echo "" # 换行 if [ -z "$admin_password" ]; then log_warning "密码不能为空,请重新输入" continue fi echo -n "请再次确认密码: " read -rs admin_password_confirm < /dev/tty echo "" # 换行 if [ "$admin_password" != "$admin_password_confirm" ]; then log_warning "两次输入的密码不一致,请重新输入" continue fi break done log_info "初始化管理员密码..." docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX && php artisan account:init-admin-password '$admin_password'" || { log_warning "管理员密码初始化失败" } log_success "管理员密码初始化完成" } # =========================================== # Crontab 与队列(Supervisor) # =========================================== configure_cron_and_supervisor_queues() { log_info "==========================================" log_info "配置 Crontab 定时任务与队列(Supervisor)..." log_info "==========================================" if ! is_container_running; then log_warning "容器未运行,跳过 Crontab 与队列配置" return 0 fi local cron_dir="$PROJECT_ROOT/docker-dev/cron" local sup_dir="$PROJECT_ROOT/docker-dev/supervisor" if [ -d "$cron_dir" ]; then local has_cron=false for f in "$cron_dir"/*; do [ -e "$f" ] || continue [ -f "$f" ] || continue has_cron=true local user user=$(basename "$f") log_info "安装 crontab(用户: $user): $f" docker cp "$f" "$CONTAINER_NAME:/tmp/crontab.install.$user" if docker exec "$CONTAINER_NAME" sh -c "crontab -u "$user" "/tmp/crontab.install.$user"" 2>/dev/null; then log_success "crontab 已安装: $user" else log_warning "crontab 安装失败(用户: $user)。请确认镜像已包含 dcron 且 supervisord 已配置 [program:crond],必要时执行: $0 --rebuild" fi done if [ "$has_cron" = false ]; then log_warning "目录 $cron_dir 下无 crontab 文件" else if docker exec "$CONTAINER_NAME" supervisorctl status crond &>/dev/null; then docker exec "$CONTAINER_NAME" supervisorctl restart crond 2>/dev/null || true fi fi else log_warning "未找到目录: $cron_dir,跳过 Crontab" fi if [ ! -d "$sup_dir" ]; then log_warning "未找到目录: $sup_dir,跳过队列配置" return 0 fi local has_sup=false for ini in "$sup_dir"/*.ini; do [ -f "$ini" ] || continue has_sup=true local base base=$(basename "$ini" .ini) log_info "安装 Supervisor 片段: ${base}.conf" docker cp "$ini" "$CONTAINER_NAME:/etc/supervisor/conf.d/${base}.conf" done if [ "$has_sup" = false ]; then log_warning "目录 $sup_dir 下无 .ini 配置" return 0 fi log_info "重新加载 Supervisor 并应用队列配置..." if docker exec "$CONTAINER_NAME" supervisorctl reread 2>/dev/null && \ docker exec "$CONTAINER_NAME" supervisorctl update 2>/dev/null; then log_success "Supervisor 已重新加载,队列进程应已启动" else log_warning "supervisorctl reread/update 未完全成功,尝试 reload..." docker exec "$CONTAINER_NAME" supervisorctl reload 2>/dev/null || { log_warning "Supervisor 重载失败,请重启容器: docker restart $CONTAINER_NAME" } fi log_success "Crontab 与队列配置步骤完成" } # =========================================== # 检查容器内目录是否存在并挂载正确 # =========================================== check_container_directory() { local container_path=$1 local host_path=$2 local project_name=$3 local max_retries=2 local retry_count=0 # 首先检查容器是否运行 if ! is_container_running; then log_warning "容器未运行,无法检查目录挂载状态" log_info "目录挂载将在容器启动后自动生效" # 对于 ECShopX_desktop-frontend,如果容器未运行,也返回成功(允许继续) if [ "$project_name" = "ECShopX_desktop-frontend" ]; then return 0 fi return 0 # 容器未运行时,假设挂载会在启动后生效 fi while [ $retry_count -lt $max_retries ]; do # 检查目录是否存在 if docker exec "$CONTAINER_NAME" sh -c "test -d $container_path" 2>/dev/null; then # 检查 package.json 是否存在 if docker exec "$CONTAINER_NAME" sh -c "test -f $container_path/package.json" 2>/dev/null; then log_success "目录挂载检查通过: $container_path" return 0 # 目录和文件都存在 else if [ $retry_count -eq 0 ]; then log_warning "容器内 $container_path/package.json 不存在,尝试重启容器以确保目录正确挂载..." if ! restart_container_for_mount "$project_name"; then return 1 fi retry_count=$((retry_count + 1)) continue else # 对于 ECShopX_desktop-frontend,如果重启后仍然不存在,也允许继续(可能是配置问题) if [ "$project_name" = "ECShopX_desktop-frontend" ]; then log_warning "容器内 $container_path/package.json 仍然不存在,但继续执行..." return 0 fi log_error "容器内 $container_path/package.json 仍然不存在" log_info "请检查主机目录 $host_path 是否存在且包含 package.json" return 1 fi fi else if [ $retry_count -eq 0 ]; then log_warning "容器内 $container_path 目录不存在,尝试重启容器以确保目录正确挂载..." if ! restart_container_for_mount "$project_name"; then return 1 fi retry_count=$((retry_count + 1)) continue else log_error "容器内 $container_path 目录仍然不存在" log_info "请检查 docker-compose.dev.yml 中的卷挂载配置是否正确" log_info "主机目录路径: $host_path" return 1 fi fi done return 1 } # =========================================== # 重启容器以确保目录挂载 # =========================================== restart_container_for_mount() { local project_name=$1 log_warning "检测到 $project_name 目录未正确挂载,正在重启容器..." if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$" 2>/dev/null; then log_info "重启容器 $CONTAINER_NAME..." docker-compose -f "$DOCKER_COMPOSE_FILE" restart || { log_error "容器重启失败" return 1 } log_info "等待容器启动..." sleep 5 # 等待服务就绪 wait_for_services log_success "容器重启完成" return 0 else log_warning "容器未运行,无需重启" return 0 fi } # =========================================== # 配置前端项目的 .env 文件 # =========================================== configure_frontend_env() { local project_dir=$1 local project_name=$2 local api_base_url=${3:-"http://localhost:8080/api/"} local app_id=${4:-""} local default_lang=${5:-""} # 检查主机目录是否存在 if [ ! -d "$project_dir" ]; then log_warning "$project_name 目录不存在,跳过配置" return 0 fi # 检查容器内目录是否存在 local container_path="" if [ "$project_name" = "ECShopX_admin-frontend" ]; then container_path="/data/httpd/ECShopX_admin-frontend" elif [ "$project_name" = "ECShopX_mobile-frontend" ]; then container_path="/data/httpd/ECShopX_mobile-frontend" elif [ "$project_name" = "ECShopX_desktop-frontend" ]; then container_path="/data/httpd/ECShopX_desktop-frontend" else log_warning "未知的项目名称: $project_name" return 0 fi # 如果容器未运行,直接配置主机目录 if ! is_container_running; then log_info "容器未运行,配置主机目录的 .env 文件..." local env_file="$project_dir/.env" # 如果 .env 不存在,尝试从 .env.example 复制 if [ ! -f "$env_file" ]; then if [ -f "$project_dir/.env.example" ]; then log_info "从 .env.example 创建 .env 文件..." cp "$project_dir/.env.example" "$env_file" else log_info "创建新的 .env 文件..." touch "$env_file" fi fi # 配置 ECShopX_admin-frontend if [ "$project_name" = "ECShopX_admin-frontend" ]; then # 使用 sed 修改或添加 VUE_APP_BASE_API if grep -q "^VUE_APP_BASE_API=" "$env_file" 2>/dev/null; then # macOS 和 Linux 兼容的 sed 命令 if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s|^VUE_APP_BASE_API=.*|VUE_APP_BASE_API=$api_base_url|" "$env_file" else sed -i "s|^VUE_APP_BASE_API=.*|VUE_APP_BASE_API=$api_base_url|" "$env_file" fi else echo "VUE_APP_BASE_API=$api_base_url" >> "$env_file" fi log_success "已配置 VUE_APP_BASE_API=$api_base_url" # 配置 VUE_APP_DEFAULT_LANG(如果提供) if [ -n "$default_lang" ]; then if grep -q "^VUE_APP_DEFAULT_LANG=" "$env_file" 2>/dev/null; then # macOS 和 Linux 兼容的 sed 命令 if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s|^VUE_APP_DEFAULT_LANG=.*|VUE_APP_DEFAULT_LANG=$default_lang|" "$env_file" else sed -i "s|^VUE_APP_DEFAULT_LANG=.*|VUE_APP_DEFAULT_LANG=$default_lang|" "$env_file" fi else echo "VUE_APP_DEFAULT_LANG=$default_lang" >> "$env_file" fi log_success "已配置 VUE_APP_DEFAULT_LANG=$default_lang" fi # 配置 VUE_APP_QIANKUN_ENTRY if grep -q "^VUE_APP_QIANKUN_ENTRY=" "$env_file" 2>/dev/null; then # macOS 和 Linux 兼容的 sed 命令 if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s|^VUE_APP_QIANKUN_ENTRY=.*|VUE_APP_QIANKUN_ENTRY=http://localhost:8080/newpc/|" "$env_file" else sed -i "s|^VUE_APP_QIANKUN_ENTRY=.*|VUE_APP_QIANKUN_ENTRY=http://localhost:8080/newpc/|" "$env_file" fi else echo "VUE_APP_QIANKUN_ENTRY=http://localhost:8080/newpc/" >> "$env_file" fi log_success "已配置 VUE_APP_QIANKUN_ENTRY=http://localhost:8080/newpc/" fi # 配置 ECShopX_mobile-frontend if [ "$project_name" = "ECShopX_mobile-frontend" ]; then # 使用 sed 修改或添加 APP_BASE_URL if grep -q "^APP_BASE_URL=" "$env_file" 2>/dev/null; then # macOS 和 Linux 兼容的 sed 命令 if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s|^APP_BASE_URL=.*|APP_BASE_URL=$api_base_url|" "$env_file" else sed -i "s|^APP_BASE_URL=.*|APP_BASE_URL=$api_base_url|" "$env_file" fi else echo "APP_BASE_URL=$api_base_url" >> "$env_file" fi log_success "已配置 APP_BASE_URL=$api_base_url" # 配置 APP_PLATFORM(如果提供) if [ -n "$app_id" ]; then if grep -q "^APP_PLATFORM=" "$env_file" 2>/dev/null; then # macOS 和 Linux 兼容的 sed 命令 if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s|^APP_PLATFORM=.*|APP_PLATFORM=$app_id|" "$env_file" else sed -i "s|^APP_PLATFORM=.*|APP_PLATFORM=$app_id|" "$env_file" fi else echo "APP_PLATFORM=$app_id" >> "$env_file" fi log_success "已配置 APP_PLATFORM=$app_id" fi # 配置 APP_I18N_ORIGIN_LANG(如果提供) if [ -n "$default_lang" ]; then if grep -q "^APP_I18N_ORIGIN_LANG=" "$env_file" 2>/dev/null; then # macOS 和 Linux 兼容的 sed 命令 if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s|^APP_I18N_ORIGIN_LANG=.*|APP_I18N_ORIGIN_LANG=$default_lang|" "$env_file" else sed -i "s|^APP_I18N_ORIGIN_LANG=.*|APP_I18N_ORIGIN_LANG=$default_lang|" "$env_file" fi else echo "APP_I18N_ORIGIN_LANG=$default_lang" >> "$env_file" fi log_success "已配置 APP_I18N_ORIGIN_LANG=$default_lang" fi fi # 配置 ECShopX_desktop-frontend if [ "$project_name" = "ECShopX_desktop-frontend" ]; then # 使用 sed 修改或添加 VUE_APP_API_BASE_URL if grep -q "^VUE_APP_API_BASE_URL=" "$env_file" 2>/dev/null; then # macOS 和 Linux 兼容的 sed 命令 if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s|^VUE_APP_API_BASE_URL=.*|VUE_APP_API_BASE_URL=$api_base_url|" "$env_file" else sed -i "s|^VUE_APP_API_BASE_URL=.*|VUE_APP_API_BASE_URL=$api_base_url|" "$env_file" fi else echo "VUE_APP_API_BASE_URL=$api_base_url" >> "$env_file" fi log_success "已配置 VUE_APP_API_BASE_URL=$api_base_url" # 配置 VUE_APP_COMPANYID(默认值为1) if grep -q "^VUE_APP_COMPANYID=" "$env_file" 2>/dev/null; then # macOS 和 Linux 兼容的 sed 命令 if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s|^VUE_APP_COMPANYID=.*|VUE_APP_COMPANYID=1|" "$env_file" else sed -i "s|^VUE_APP_COMPANYID=.*|VUE_APP_COMPANYID=1|" "$env_file" fi else echo "VUE_APP_COMPANYID=1" >> "$env_file" fi log_success "已配置 VUE_APP_COMPANYID=1" # 配置 VUE_APP_DEFAULT_LANG(如果提供) if [ -n "$default_lang" ]; then if grep -q "^VUE_APP_DEFAULT_LANG=" "$env_file" 2>/dev/null; then # macOS 和 Linux 兼容的 sed 命令 if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s|^VUE_APP_DEFAULT_LANG=.*|VUE_APP_DEFAULT_LANG=$default_lang|" "$env_file" else sed -i "s|^VUE_APP_DEFAULT_LANG=.*|VUE_APP_DEFAULT_LANG=$default_lang|" "$env_file" fi else echo "VUE_APP_DEFAULT_LANG=$default_lang" >> "$env_file" fi log_success "已配置 VUE_APP_DEFAULT_LANG=$default_lang" fi fi else # 容器运行中,配置容器内的 .env 文件 # 检查容器内目录是否存在 if ! docker exec "$CONTAINER_NAME" sh -c "test -d $container_path" 2>/dev/null; then log_warning "容器内 $container_path 目录不存在,跳过配置" return 0 fi # 在容器内配置 .env 文件 docker exec "$CONTAINER_NAME" sh -c " cd $container_path && \ if [ ! -f .env ]; then if [ -f .env.example ]; then cp .env.example .env else touch .env fi fi " 2>/dev/null || true # 配置 ECShopX_admin-frontend if [ "$project_name" = "ECShopX_admin-frontend" ]; then docker exec "$CONTAINER_NAME" sh -c " cd $container_path && \ if grep -q '^VUE_APP_BASE_API=' .env 2>/dev/null; then sed -i 's|^VUE_APP_BASE_API=.*|VUE_APP_BASE_API=$api_base_url|' .env else echo 'VUE_APP_BASE_API=$api_base_url' >> .env fi " 2>/dev/null || true log_success "已配置容器内 VUE_APP_BASE_API=$api_base_url" # 配置 VUE_APP_DEFAULT_LANG(如果提供) if [ -n "$default_lang" ]; then docker exec "$CONTAINER_NAME" sh -c " cd $container_path && \ if grep -q '^VUE_APP_DEFAULT_LANG=' .env 2>/dev/null; then sed -i 's|^VUE_APP_DEFAULT_LANG=.*|VUE_APP_DEFAULT_LANG=$default_lang|' .env else echo 'VUE_APP_DEFAULT_LANG=$default_lang' >> .env fi " 2>/dev/null || true log_success "已配置容器内 VUE_APP_DEFAULT_LANG=$default_lang" fi # 配置 VUE_APP_QIANKUN_ENTRY docker exec "$CONTAINER_NAME" sh -c " cd $container_path && \ if grep -q '^VUE_APP_QIANKUN_ENTRY=' .env 2>/dev/null; then sed -i 's|^VUE_APP_QIANKUN_ENTRY=.*|VUE_APP_QIANKUN_ENTRY=http://localhost:8080/newpc/|' .env else echo 'VUE_APP_QIANKUN_ENTRY=http://localhost:8080/newpc/' >> .env fi " 2>/dev/null || true log_success "已配置容器内 VUE_APP_QIANKUN_ENTRY=http://localhost:8080/newpc/" fi # 配置 ECShopX_mobile-frontend if [ "$project_name" = "ECShopX_mobile-frontend" ]; then docker exec "$CONTAINER_NAME" sh -c " cd $container_path && \ if grep -q '^APP_BASE_URL=' .env 2>/dev/null; then sed -i 's|^APP_BASE_URL=.*|APP_BASE_URL=$api_base_url|' .env else echo 'APP_BASE_URL=$api_base_url' >> .env fi " 2>/dev/null || true log_success "已配置容器内 APP_BASE_URL=$api_base_url" # 配置 APP_PLATFORM(如果提供) if [ -n "$app_id" ]; then docker exec "$CONTAINER_NAME" sh -c " cd $container_path && \ if grep -q '^APP_PLATFORM=' .env 2>/dev/null; then sed -i 's|^APP_PLATFORM=.*|APP_PLATFORM=$app_id|' .env else echo 'APP_PLATFORM=$app_id' >> .env fi " 2>/dev/null || true log_success "已配置容器内 APP_PLATFORM=$app_id" fi # 配置 APP_I18N_ORIGIN_LANG(如果提供) if [ -n "$default_lang" ]; then docker exec "$CONTAINER_NAME" sh -c " cd $container_path && \ if grep -q '^APP_I18N_ORIGIN_LANG=' .env 2>/dev/null; then sed -i 's|^APP_I18N_ORIGIN_LANG=.*|APP_I18N_ORIGIN_LANG=$default_lang|' .env else echo 'APP_I18N_ORIGIN_LANG=$default_lang' >> .env fi " 2>/dev/null || true log_success "已配置容器内 APP_I18N_ORIGIN_LANG=$default_lang" fi fi # 配置 ECShopX_desktop-frontend if [ "$project_name" = "ECShopX_desktop-frontend" ]; then docker exec "$CONTAINER_NAME" sh -c " cd $container_path && \ if grep -q '^VUE_APP_API_BASE_URL=' .env 2>/dev/null; then sed -i 's|^VUE_APP_API_BASE_URL=.*|VUE_APP_API_BASE_URL=$api_base_url|' .env else echo 'VUE_APP_API_BASE_URL=$api_base_url' >> .env fi " 2>/dev/null || true log_success "已配置容器内 VUE_APP_API_BASE_URL=$api_base_url" # 配置 VUE_APP_COMPANYID(默认值为1) docker exec "$CONTAINER_NAME" sh -c " cd $container_path && \ if grep -q '^VUE_APP_COMPANYID=' .env 2>/dev/null; then sed -i 's|^VUE_APP_COMPANYID=.*|VUE_APP_COMPANYID=1|' .env else echo 'VUE_APP_COMPANYID=1' >> .env fi " 2>/dev/null || true log_success "已配置容器内 VUE_APP_COMPANYID=1" # 配置 VUE_APP_DEFAULT_LANG(如果提供) if [ -n "$default_lang" ]; then docker exec "$CONTAINER_NAME" sh -c " cd $container_path && \ if grep -q '^VUE_APP_DEFAULT_LANG=' .env 2>/dev/null; then sed -i 's|^VUE_APP_DEFAULT_LANG=.*|VUE_APP_DEFAULT_LANG=$default_lang|' .env else echo 'VUE_APP_DEFAULT_LANG=$default_lang' >> .env fi " 2>/dev/null || true log_success "已配置容器内 VUE_APP_DEFAULT_LANG=$default_lang" fi fi fi } # =========================================== # 编译 ECShopX_admin-frontend # =========================================== build_admin() { if [ "$SKIP_ADMIN" = true ]; then log_info "跳过 ECShopX_admin-frontend 编译(--skip-admin)" return 0 fi ADMIN_DIR="$PARENT_DIR/ECShopX_admin-frontend" if [ ! -d "$ADMIN_DIR" ]; then log_warning "ECShopX_admin-frontend 目录不存在,跳过编译" return 0 fi log_info "==========================================" log_info "开始编译 ECShopX_admin-frontend(管理后台)..." log_info "==========================================" if [ ! -f "$ADMIN_DIR/package.json" ]; then log_error "ECShopX_admin-frontend/package.json 不存在" return 1 fi # 检查是否已有编译产物 if [ -d "$ADMIN_DIR/dist" ] && [ -f "$ADMIN_DIR/dist/index.html" ]; then log_info "检测到已有编译产物,跳过编译" log_info "如需重新编译,请删除 ECShopX_admin-frontend/dist 目录" return 0 fi # 检查容器内目录是否存在并确保正确挂载 log_info "检查容器内目录挂载状态..." if ! check_container_directory "/data/httpd/ECShopX_admin-frontend" "$ADMIN_DIR" "ECShopX_admin-frontend"; then return 1 fi # 确认默认语言(如果还未选择) if [ -z "$SELECTED_LANG" ]; then echo "" log_info "请选择默认语言:" log_info " 1) 中文 (zhcn)" log_info " 2) 英文 (en)" echo "" while true; do read -r -p "请输入选项 (1 或 2,默认: 1): " LANG_CHOICE < /dev/tty LANG_CHOICE=${LANG_CHOICE:-1} if [ "$LANG_CHOICE" = "1" ] || [ "$LANG_CHOICE" = "zhcn" ] || [ "$LANG_CHOICE" = "zh" ]; then SELECTED_LANG="zhcn" break elif [ "$LANG_CHOICE" = "2" ] || [ "$LANG_CHOICE" = "en" ] || [ "$LANG_CHOICE" = "english" ]; then SELECTED_LANG="en" break else log_error "无效的选项,请输入 1 或 2" fi done echo "" log_info "已选择默认语言: $SELECTED_LANG" echo "" else log_info "复用已选择的默认语言: $SELECTED_LANG" fi # 配置前端项目的 .env 文件 log_info "配置 ECShopX_admin-frontend 的 .env 文件..." configure_frontend_env "$ADMIN_DIR" "ECShopX_admin-frontend" "http://localhost:8080/api/" "" "$SELECTED_LANG" log_info "安装 npm 依赖..." # 使用绝对路径并先验证目录存在 docker exec "$CONTAINER_NAME" sh -c " if [ ! -d /data/httpd/ECShopX_admin-frontend ]; then echo '错误: 目录不存在' exit 1 fi cd /data/httpd/ECShopX_admin-frontend || exit 1 npm install --legacy-peer-deps " > /tmp/npm_admin_output.log 2>&1 & local npm_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $npm_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 安装 ECShopX_admin-frontend npm 依赖中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $npm_pid local npm_exit=$? if [ $npm_exit -ne 0 ]; then printf "\r${RED}[ERROR]${NC} ECShopX_admin-frontend npm install 失败" printf "%50s" "" echo "" cat /tmp/npm_admin_output.log 2>/dev/null | tail -20 log_info "请检查容器内目录状态: docker exec $CONTAINER_NAME ls -la /data/httpd/ECShopX_admin-frontend" return 1 else printf "\r${GREEN}[SUCCESS]${NC} ECShopX_admin-frontend npm 依赖安装完成" printf "%50s" "" echo "" fi # 从 ECShopX 配置文件读取业务模式 read_product_model_from_env if [ "$SELECTED_PLATFORM" = "standard" ]; then BUILD_CMD="build:b2c" BUILD_MODE_NAME="B2C" else BUILD_CMD="build:bbc" BUILD_MODE_NAME="BBC (B2B2C)" fi log_info "使用业务模式: $BUILD_MODE_NAME" echo "" log_info "开始前端环境编译" log_info "编译时长依赖本地硬件性能,耗时较长请耐心等待" log_info "举列:Mac M2芯片预计整体耗时15分钟" echo "" log_info "执行编译命令: npm run $BUILD_CMD" docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX_admin-frontend && npm run $BUILD_CMD" > /tmp/build_admin_output.log 2>&1 & local build_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $build_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 编译 ECShopX_admin-frontend 中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $build_pid local build_exit=$? if [ $build_exit -ne 0 ]; then printf "\r${RED}[ERROR]${NC} ECShopX_admin-frontend 编译失败" printf "%50s" "" echo "" cat /tmp/build_admin_output.log 2>/dev/null | tail -20 return 1 else printf "\r${GREEN}[SUCCESS]${NC} ECShopX_admin-frontend 编译完成" printf "%50s" "" echo "" fi if docker exec "$CONTAINER_NAME" sh -c "[ -f /data/httpd/ECShopX_admin-frontend/dist/index.html ]"; then log_success "ECShopX_admin-frontend 编译成功" return 0 else log_error "ECShopX_admin-frontend 编译输出不完整" return 1 fi } # =========================================== # 编译 ECShopX_desktop-frontend # =========================================== build_pc() { if [ "$SKIP_PC" = true ]; then log_info "跳过 ECShopX_desktop-frontend 编译(--skip-pc)" return 0 fi PC_DIR="$PARENT_DIR/ECShopX_desktop-frontend" if [ ! -d "$PC_DIR" ]; then log_warning "ECShopX_desktop-frontend 目录不存在,跳过编译" return 0 fi log_info "==========================================" log_info "开始编译 ECShopX_desktop-frontend(PC前端)..." log_info "==========================================" if [ ! -f "$PC_DIR/package.json" ]; then log_error "ECShopX_desktop-frontend/package.json 不存在" return 1 fi # 检查是否已有编译产物(Nuxt项目编译后生成.nuxt或.output目录) if docker exec "$CONTAINER_NAME" sh -c "[ -d /data/httpd/ECShopX_desktop-frontend/.nuxt ]" 2>/dev/null || \ docker exec "$CONTAINER_NAME" sh -c "[ -d /data/httpd/ECShopX_desktop-frontend/.output ]" 2>/dev/null || \ [ -d "$PC_DIR/.nuxt" ] || [ -d "$PC_DIR/.output" ]; then log_info "检测到已有编译产物,跳过编译" log_info "如需重新编译,请删除 ECShopX_desktop-frontend/.nuxt 或 .output 目录" # 即使跳过编译,也需要启动Nuxt服务 need_build=false else need_build=true fi # 检查容器内目录是否存在并确保正确挂载 log_info "检查容器内目录挂载状态..." if ! check_container_directory "/data/httpd/ECShopX_desktop-frontend" "$PC_DIR" "ECShopX_desktop-frontend"; then return 1 fi # 如果跳过编译,直接启动Nuxt服务,不需要确认语言 if [ "$need_build" = false ]; then # 启动 Nuxt 服务 log_info "启动 Nuxt 服务(监听3000端口)..." # 检查是否已有Nuxt进程在运行 if docker exec "$CONTAINER_NAME" sh -c "pgrep -f 'nuxt.*3000\|node.*nuxt' > /dev/null" 2>/dev/null; then log_info "Nuxt 服务已在运行" else # 启动Nuxt服务 if docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX_desktop-frontend && npm run | grep -q 'start'" 2>/dev/null; then docker exec -d "$CONTAINER_NAME" sh -c " cd /data/httpd/ECShopX_desktop-frontend && \ PORT=3000 HOST=0.0.0.0 nohup npm run start > /var/log/nuxt.log 2>&1 & " 2>/dev/null || true else docker exec -d "$CONTAINER_NAME" sh -c " cd /data/httpd/ECShopX_desktop-frontend && \ PORT=3000 HOST=0.0.0.0 nohup npm run dev > /var/log/nuxt.log 2>&1 & " 2>/dev/null || true fi # 等待Nuxt服务启动 sleep 3 fi # 重新加载nginx配置 docker exec "$CONTAINER_NAME" sh -c "nginx -s reload" 2>/dev/null || true return 0 fi # 只有在需要编译时才确认默认语言和配置.env if [ "$need_build" = true ]; then # 确认默认语言(如果还未选择) if [ -z "$SELECTED_LANG" ]; then echo "" log_info "请选择默认语言:" log_info " 1) 中文 (zhcn)" log_info " 2) 英文 (en)" echo "" while true; do read -r -p "请输入选项 (1 或 2,默认: 1): " LANG_CHOICE < /dev/tty LANG_CHOICE=${LANG_CHOICE:-1} if [ "$LANG_CHOICE" = "1" ] || [ "$LANG_CHOICE" = "zhcn" ] || [ "$LANG_CHOICE" = "zh" ]; then SELECTED_LANG="zhcn" break elif [ "$LANG_CHOICE" = "2" ] || [ "$LANG_CHOICE" = "en" ] || [ "$LANG_CHOICE" = "english" ]; then SELECTED_LANG="en" break else log_error "无效的选项,请输入 1 或 2" fi done echo "" log_info "已选择默认语言: $SELECTED_LANG" echo "" else log_info "复用已选择的默认语言: $SELECTED_LANG" fi # 配置前端项目的 .env 文件 log_info "配置 ECShopX_desktop-frontend 的 .env 文件..." configure_frontend_env "$PC_DIR" "ECShopX_desktop-frontend" "http://localhost:8080" "" "$SELECTED_LANG" log_info "安装 npm 依赖..." # 使用绝对路径并先验证目录存在 docker exec "$CONTAINER_NAME" sh -c " if [ ! -d /data/httpd/ECShopX_desktop-frontend ]; then echo '错误: 目录不存在' exit 1 fi cd /data/httpd/ECShopX_desktop-frontend || exit 1 npm install --legacy-peer-deps " > /tmp/npm_pc_output.log 2>&1 & local npm_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $npm_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 安装 ECShopX_desktop-frontend npm 依赖中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $npm_pid local npm_exit=$? if [ $npm_exit -ne 0 ]; then printf "\r${RED}[ERROR]${NC} ECShopX_desktop-frontend npm install 失败" printf "%50s" "" echo "" cat /tmp/npm_pc_output.log 2>/dev/null | tail -20 log_info "请检查容器内目录状态: docker exec $CONTAINER_NAME ls -la /data/httpd/ECShopX_desktop-frontend" return 1 else printf "\r${GREEN}[SUCCESS]${NC} ECShopX_desktop-frontend npm 依赖安装完成" printf "%50s" "" echo "" fi log_info "执行编译(npm run build)..." docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX_desktop-frontend && npm run build" > /tmp/build_pc_output.log 2>&1 & local build_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $build_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 编译 ECShopX_desktop-frontend 中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $build_pid local build_exit=$? if [ $build_exit -ne 0 ]; then printf "\r${RED}[ERROR]${NC} ECShopX_desktop-frontend 编译失败" printf "%50s" "" echo "" cat /tmp/build_pc_output.log 2>/dev/null | tail -20 return 1 else printf "\r${GREEN}[SUCCESS]${NC} ECShopX_desktop-frontend 编译完成" printf "%50s" "" echo "" fi # 验证编译产物 if ! docker exec "$CONTAINER_NAME" sh -c "[ -d /data/httpd/ECShopX_desktop-frontend/.nuxt ] || [ -d /data/httpd/ECShopX_desktop-frontend/.output ]" 2>/dev/null; then log_error "ECShopX_desktop-frontend 编译输出不完整" return 1 fi fi # 检查编译产物(Nuxt项目编译后生成.nuxt或.output目录) if docker exec "$CONTAINER_NAME" sh -c "[ -d /data/httpd/ECShopX_desktop-frontend/.nuxt ] || [ -d /data/httpd/ECShopX_desktop-frontend/.output ]" 2>/dev/null; then log_success "ECShopX_desktop-frontend 编译成功" # 启动 Nuxt 服务 log_info "启动 Nuxt 服务(监听3000端口)..." # 检查是否已有Nuxt进程在运行 if docker exec "$CONTAINER_NAME" sh -c "pgrep -f 'nuxt.*3000\|node.*nuxt' > /dev/null" 2>/dev/null; then log_info "Nuxt 服务已在运行,重启服务..." docker exec "$CONTAINER_NAME" sh -c "pkill -f 'nuxt.*3000\|node.*nuxt'" 2>/dev/null || true sleep 2 fi # 在后台启动Nuxt服务 # 优先使用生产模式(npm run start),如果不存在则使用开发模式(npm run dev) log_info "检查可用的启动命令..." if docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX_desktop-frontend && npm run | grep -q 'start'" 2>/dev/null; then log_info "使用生产模式启动(npm run start)..." docker exec -d "$CONTAINER_NAME" sh -c " cd /data/httpd/ECShopX_desktop-frontend && \ PORT=3000 HOST=0.0.0.0 nohup npm run start > /var/log/nuxt.log 2>&1 & " || { log_error "Nuxt 服务启动失败" log_info "请检查日志: docker exec $CONTAINER_NAME tail -f /var/log/nuxt.log" return 1 } else log_info "使用开发模式启动(npm run dev)..." docker exec -d "$CONTAINER_NAME" sh -c " cd /data/httpd/ECShopX_desktop-frontend && \ PORT=3000 HOST=0.0.0.0 nohup npm run dev > /var/log/nuxt.log 2>&1 & " || { log_error "Nuxt 服务启动失败" log_info "请检查日志: docker exec $CONTAINER_NAME tail -f /var/log/nuxt.log" return 1 } fi # 等待Nuxt服务启动 log_info "等待 Nuxt 服务启动..." local nuxt_ready=false for i in {1..30}; do if docker exec "$CONTAINER_NAME" sh -c "curl -s http://127.0.0.1:3000 > /dev/null" 2>/dev/null; then nuxt_ready=true break fi sleep 2 if [ $((i % 5)) -eq 0 ]; then printf "\r${CYAN}[INFO]${NC} 等待 Nuxt 服务启动... ($i/30)" fi done if [ "$nuxt_ready" = true ]; then printf "\r${GREEN}[SUCCESS]${NC} Nuxt 服务已启动并监听3000端口" printf "%50s" "" echo "" if [ "$INSTALLED_VSHOP" = true ]; then log_info "H5前端访问地址: http://localhost:8081" fi if [ "$INSTALLED_PC" = true ]; then log_info "PC前端访问地址: http://localhost:8082" fi else printf "\r${YELLOW}[WARNING]${NC} Nuxt 服务启动超时,但可能仍在启动中" printf "%50s" "" echo "" log_info "请检查日志: docker exec $CONTAINER_NAME tail -f /var/log/nuxt.log" if [ "$INSTALLED_VSHOP" = true ]; then log_info "H5前端访问地址: http://localhost:8081" fi if [ "$INSTALLED_PC" = true ]; then log_info "PC前端访问地址: http://localhost:8082" fi fi # 重新加载nginx配置以确保8082端口配置生效 log_info "重新加载 Nginx 配置..." docker exec "$CONTAINER_NAME" sh -c "nginx -s reload" 2>/dev/null || { log_warning "Nginx 配置重载失败,可能需要重启容器" } else log_error "ECShopX_desktop-frontend 编译输出不完整" return 1 fi } # =========================================== # 编译 ECShopX_mobile-frontend # =========================================== build_vshop() { if [ "$SKIP_VSHOP" = true ]; then log_info "跳过 ECShopX_mobile-frontend 编译(--skip-vshop)" return 0 fi VSHOP_DIR="$PARENT_DIR/ECShopX_mobile-frontend" if [ ! -d "$VSHOP_DIR" ]; then log_warning "ECShopX_mobile-frontend 目录不存在,跳过编译" return 0 fi log_info "==========================================" log_info "开始编译 ECShopX_mobile-frontend(H5前端)..." log_info "==========================================" if [ ! -f "$VSHOP_DIR/package.json" ]; then log_error "ECShopX_mobile-frontend/package.json 不存在" return 1 fi # 检查是否已有编译产物 if [ -d "$VSHOP_DIR/dist" ]; then log_info "检测到已有编译产物,跳过编译" log_info "如需重新编译,请删除 ECShopX_mobile-frontend/dist 目录" return 0 fi # 检查容器内目录是否存在并确保正确挂载 log_info "检查容器内目录挂载状态..." if ! check_container_directory "/data/httpd/ECShopX_mobile-frontend" "$VSHOP_DIR" "ECShopX_mobile-frontend"; then return 1 fi # 从 ECShopX 配置文件读取业务模式 read_product_model_from_env if [ "$SELECTED_PLATFORM" = "standard" ]; then BUILD_MODE_NAME="B2C" else BUILD_MODE_NAME="BBC (B2B2C)" fi log_info "使用业务模式: $BUILD_MODE_NAME" # 确认默认语言(如果还未选择) if [ -z "$SELECTED_LANG" ]; then echo "" log_info "请选择默认语言:" log_info " 1) 中文 (zhcn)" log_info " 2) 英文 (en)" echo "" while true; do read -r -p "请输入选项 (1 或 2,默认: 1): " LANG_CHOICE < /dev/tty LANG_CHOICE=${LANG_CHOICE:-1} if [ "$LANG_CHOICE" = "1" ] || [ "$LANG_CHOICE" = "zhcn" ] || [ "$LANG_CHOICE" = "zh" ]; then SELECTED_LANG="zhcn" break elif [ "$LANG_CHOICE" = "2" ] || [ "$LANG_CHOICE" = "en" ] || [ "$LANG_CHOICE" = "english" ]; then SELECTED_LANG="en" break else log_error "无效的选项,请输入 1 或 2" fi done echo "" log_info "已选择默认语言: $SELECTED_LANG" echo "" else log_info "复用已选择的默认语言: $SELECTED_LANG" fi # 配置前端项目的 .env 文件 log_info "配置 ECShopX_mobile-frontend 的 .env 文件..." configure_frontend_env "$VSHOP_DIR" "ECShopX_mobile-frontend" "http://localhost:8080/api/h5app/wxapp" "$SELECTED_PLATFORM" "$SELECTED_LANG" log_info "安装 npm 依赖..." # 使用绝对路径并先验证目录存在 docker exec "$CONTAINER_NAME" sh -c " if [ ! -d /data/httpd/ECShopX_mobile-frontend ]; then echo '错误: 目录不存在' exit 1 fi cd /data/httpd/ECShopX_mobile-frontend || exit 1 npm install --legacy-peer-deps " > /tmp/npm_vshop_output.log 2>&1 & local npm_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $npm_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 安装 ECShopX_mobile-frontend npm 依赖中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $npm_pid local npm_exit=$? if [ $npm_exit -ne 0 ]; then printf "\r${RED}[ERROR]${NC} ECShopX_mobile-frontend npm install 失败" printf "%50s" "" echo "" cat /tmp/npm_vshop_output.log 2>/dev/null | tail -20 log_info "请检查容器内目录状态: docker exec $CONTAINER_NAME ls -la /data/httpd/ECShopX_mobile-frontend" return 1 else printf "\r${GREEN}[SUCCESS]${NC} ECShopX_mobile-frontend npm 依赖安装完成" printf "%50s" "" echo "" fi log_info "执行编译(npm run build:h5)..." docker exec "$CONTAINER_NAME" sh -c "cd /data/httpd/ECShopX_mobile-frontend && npm run build:h5" > /tmp/build_vshop_output.log 2>&1 & local build_pid=$! # 显示进度动画 local spinstr='|/-\' while kill -0 $build_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 编译 ECShopX_mobile-frontend 中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $build_pid local build_exit=$? if [ $build_exit -ne 0 ]; then printf "\r${RED}[ERROR]${NC} ECShopX_mobile-frontend 编译失败" printf "%50s" "" echo "" cat /tmp/build_vshop_output.log 2>/dev/null | tail -20 return 1 else printf "\r${GREEN}[SUCCESS]${NC} ECShopX_mobile-frontend 编译完成" printf "%50s" "" echo "" fi if docker exec "$CONTAINER_NAME" sh -c "[ -d /data/httpd/ECShopX_mobile-frontend/dist ]"; then log_success "ECShopX_mobile-frontend 编译成功" return 0 else log_error "ECShopX_mobile-frontend 编译输出目录不存在" return 1 fi } # =========================================== # 导入 Demo 数据 # =========================================== import_demo_data() { # 如果全局变量为空,尝试从 ECShopX 配置文件读取 read_product_model_from_env if [ -z "$SELECTED_PLATFORM" ]; then log_info "未选择业务模式,跳过 Demo 数据导入" return 0 fi local demo_dir="/data/httpd/ECShopX/docker-dev/demo" # 检查数据库中 items 表是否已有数据 local item_count item_count=$(docker exec "$CONTAINER_NAME" sh -c \ "mysql -u$MYSQL_USER -p'$MYSQL_PASSWORD' -h127.0.0.1 $MYSQL_DATABASE -sN -e 'SELECT COUNT(*) FROM items;'" 2>/dev/null) if [ -n "$item_count" ] && [ "$item_count" -gt 0 ] 2>/dev/null; then log_info "数据库中已存在商品数据(items 表有 ${item_count} 条记录),跳过 Demo 数据导入" return 0 fi local sql_file="" if [ "$SELECTED_PLATFORM" = "platform" ]; then # BBC 模式,只有一个 bbc.sql sql_file="bbc.sql" echo "" echo -n "是否导入 BBC Demo 数据? [Y/n]: " read -r import_answer < /dev/tty if [ -n "$import_answer" ] && [ "$import_answer" != "Y" ] && [ "$import_answer" != "y" ] && [ "$import_answer" != "yes" ] && [ "$import_answer" != "YES" ]; then log_info "跳过 Demo 数据导入" return 0 fi elif [ "$SELECTED_PLATFORM" = "standard" ]; then # B2C 模式,让用户选择行业 echo "" log_info "请选择要导入的 B2C Demo 数据(行业):" log_info " 1) 美妆" log_info " 2) 运动" log_info " 3) 包袋" log_info " 0) 跳过,不导入" echo "" while true; do read -r -p "请输入选项 (0-3,默认: 0): " DEMO_CHOICE < /dev/tty DEMO_CHOICE=${DEMO_CHOICE:-0} case "$DEMO_CHOICE" in 0) log_info "跳过 Demo 数据导入" return 0 ;; 1) sql_file="bc_beauty.sql" break ;; 2) sql_file="b2c_sports.sql" break ;; 3) sql_file="b2c_bags.sql" break ;; *) log_error "无效的选项,请输入 0-3" ;; esac done fi if [ -z "$sql_file" ]; then return 0 fi # 检查容器内 SQL 文件是否存在 if ! docker exec "$CONTAINER_NAME" sh -c "test -f '$demo_dir/$sql_file'" 2>/dev/null; then log_error "Demo 数据文件不存在: $demo_dir/$sql_file" return 1 fi log_info "正在导入 Demo 数据: $sql_file ..." docker exec "$CONTAINER_NAME" sh -c \ "mysql -u$MYSQL_USER -p'$MYSQL_PASSWORD' -h127.0.0.1 $MYSQL_DATABASE < '$demo_dir/$sql_file'" > /tmp/demo_import.log 2>&1 & local import_pid=$! local spinstr='|/-\' while kill -0 $import_pid 2>/dev/null; do local temp=${spinstr#?} printf "\r${CYAN}[INFO]${NC} 导入 Demo 数据中 ${spinstr:0:1}" spinstr=$temp${spinstr%"$temp"} sleep 0.2 done wait $import_pid local import_exit=$? if [ $import_exit -ne 0 ]; then printf "\r${RED}[ERROR]${NC} Demo 数据导入失败" printf "%50s" "" echo "" cat /tmp/demo_import.log 2>/dev/null | tail -20 return 1 else printf "\r${GREEN}[SUCCESS]${NC} Demo 数据导入完成($sql_file)" printf "%50s" "" echo "" fi } # =========================================== # 开源安装统计上报(可选) # =========================================== # 计算 MD5(大写十六进制),入参为原始字符串 open_source_stat_md5_upper() { local data="$1" if command -v md5sum &>/dev/null; then printf '%s' "$data" | md5sum | awk '{print toupper($1)}' elif command -v md5 &>/dev/null; then printf '%s' "$data" | md5 -q | tr '[:lower:]' '[:upper:]' else echo "" fi } # 按文档:除 sign 外键名升序,拼接 k + serialize(v),MD5(secret + 拼接 + secret) 大写 open_source_stat_sign() { local secret="$1" local product="$2" local instance_id="$3" local version="$4" local timestamp="$5" local s="instance_id${instance_id}product${product}timestamp${timestamp}version${version}" open_source_stat_md5_upper "${secret}${s}${secret}" } escape_json_string() { local s="$1" s="${s//\\/\\\\}" s="${s//\"/\\\"}" printf '%s' "$s" } # 从 ECShopX 项目根目录 composer.json 读取 "version" 字段(用于开源安装统计) get_ecshopx_version_from_composer() { local f="${PROJECT_ROOT}/composer.json" local v="" [ -r "$f" ] || { echo "0.0.0" return 0 } if command -v php &>/dev/null; then v=$(php -r '$j=json_decode(file_get_contents($argv[1]),true);echo isset($j["version"])?(string)$j["version"]:"";' "$f" 2>/dev/null || true) fi if [ -z "$v" ]; then v=$(sed -n '1,40s/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$f" | head -1) fi [ -n "$v" ] || v="0.0.0" echo "$v" } get_open_source_instance_id() { local mac="" if [ "${OS:-}" = "macos" ]; then mac=$(ifconfig 2>/dev/null | awk '/ether/ {print tolower($2); exit}' || true) elif [ "${OS:-}" = "linux" ]; then local f for f in /sys/class/net/*/address; do [ ! -f "$f" ] && continue case "$f" in */lo/address) continue ;; esac mac=$(tr '[:upper:]' '[:lower:]' < "$f" || true) [ -n "$mac" ] && [ "$mac" != "00:00:00:00:00:00" ] && break done fi if [ -z "$mac" ]; then mac=$(hostname 2>/dev/null || echo "unknown") fi echo "${mac:0:64}" } report_open_source_install_stat() { local gateway="https://gwnextapi.shopex.cn" local secret="aF3dG6hJ1kL9zXcV4bN2mQ" ([ -z "$gateway" ] || [ -z "$secret" ]) && return 0 command -v curl &>/dev/null || return 0 local product="echopx" local instance_id instance_id=$(get_open_source_instance_id) local version version=$(get_ecshopx_version_from_composer) local timestamp timestamp=$(date +%s) local sign sign=$(open_source_stat_sign "$secret" "$product" "$instance_id" "$version" "$timestamp") [ -z "$sign" ] && return 0 local base="${gateway%/}" local url="${base}/usercenter/open_source/stat/report" local inst_esc ver_esc inst_esc=$(escape_json_string "$instance_id") ver_esc=$(escape_json_string "$version") local body body=$(printf '{"product":"%s","instance_id":"%s","version":"%s","timestamp":%s,"sign":"%s"}' \ "$product" "$inst_esc" "$ver_esc" "$timestamp" "$sign") curl -sS --max-time 15 -o /dev/null -X POST "$url" \ -H 'Content-Type: application/json' \ -d "$body" 2>/dev/null || true return 0 } # =========================================== # 显示完成信息 # =========================================== show_success_info() { # 计算执行时间 END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) MINUTES=$((DURATION / 60)) SECONDS=$((DURATION % 60)) echo "" log_success "==========================================" log_success "恭喜你,安装成功" log_success "温馨提示:移动商城、PC商城前端显示空白,并非系统问题,需前往管理后台配置模板数据。" # 根据安装的项目显示温馨提示 if [ "$INSTALLED_VSHOP" = true ] || [ "$INSTALLED_PC" = true ]; then local tip_msg="温馨提示:" if [ "$INSTALLED_VSHOP" = true ] && [ "$INSTALLED_PC" = true ]; then tip_msg="${tip_msg}移动商城、PC商城前端显示空白,并非系统问题,需前往管理后台配置模板数据。" elif [ "$INSTALLED_VSHOP" = true ]; then tip_msg="${tip_msg}移动商城前端显示空白,并非系统问题,需前往管理后台配置模板数据。" elif [ "$INSTALLED_PC" = true ]; then tip_msg="${tip_msg}PC商城前端显示空白,并非系统问题,需前往管理后台配置模板数据。" fi log_success "$tip_msg" fi log_success "==========================================" echo "" log_info "服务信息:" if [ "$INSTALLED_ADMIN" = true ]; then log_info " 管理后台: http://localhost:8080" fi if [ "$INSTALLED_VSHOP" = true ]; then log_info " H5前端: http://localhost:8081" fi if [ "$INSTALLED_PC" = true ]; then log_info " PC前端: http://localhost:8082" fi log_info " API 接口: http://localhost:8080/api/" log_info " MySQL: localhost:3306 (用户: $MYSQL_USER, 密码: $MYSQL_PASSWORD)" log_info " Redis: localhost:6379 (密码: $REDIS_PASSWORD)" echo "" # 检查微信小程序编译产物(仅在安装了移动商城时显示) if [ "$INSTALLED_VSHOP" = true ]; then VSHOP_DIR="$PARENT_DIR/ECShopX_mobile-frontend" if [ -d "$VSHOP_DIR/dist" ]; then log_info "微信小程序:" log_info " 微信开发者工具打开目录: $VSHOP_DIR/dist" echo "" fi fi log_info "常用命令:" log_info " 查看日志: $DOCKER_COMPOSE_CMD -f $DOCKER_COMPOSE_FILE logs -f" log_info " 服务状态: docker exec $CONTAINER_NAME supervisorctl status" log_info " 进入容器: docker exec -it $CONTAINER_NAME sh" log_info " 停止服务: $DOCKER_COMPOSE_CMD -f $DOCKER_COMPOSE_FILE down" log_info " 重启服务: $DOCKER_COMPOSE_CMD -f $DOCKER_COMPOSE_FILE restart" log_info " 重新构建: $0 --rebuild" echo "" log_info "执行时间: ${MINUTES}分${SECONDS}秒" report_open_source_install_stat log_success "==========================================" } # =========================================== # 主函数 # =========================================== main() { # 解析命令行参数 parse_args "$@" echo "==========================================" echo " ECShopX 开发环境设置 (Docker)" echo " 支持: ECShopX / ECShopX_admin-frontend / ECShopX_mobile-frontend / ECShopX_desktop-frontend" echo "==========================================" echo "" detect_os check_docker # 检查并克隆前端项目(在启动容器之前) check_and_clone_frontend # 检查容器状态 if check_container_status; then run_docker fi # 配置 PHP 应用 configure_env configure_application configure_cron_and_supervisor_queues # 编译前端项目 build_admin || log_warning "管理后台(ECShopX_admin-frontend)编译未完成" build_vshop || log_warning "移动商城(ECShopX_mobile-frontend)编译未完成" build_pc || log_warning "PC商城(ECShopX_desktop-frontend)编译未完成" # 导入 Demo 数据 import_demo_data || log_warning "Demo 数据导入未完成" # Show success info show_success_info } # 执行主函数 main "$@"