Files
ECShopX/dev-setup.sh
wanghai 9b34f9c83b 4.5.0
2026-04-17 21:00:30 +08:00

2362 lines
86 KiB
Bash
Executable File
Raw Permalink 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
# 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 "开始配置 ECShopXPHP 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-frontendPC前端..."
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-frontendH5前端..."
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 "$@"