#!/bin/bash # ============================================ # 多模式部署脚本 # 用法: # bash deploy.sh init 交互式初始化 .env # bash deploy.sh start 启动 # bash deploy.sh build [分支] 编译镜像 # bash deploy.sh rebuild [分支] 拉取代码 + 编译 + 启动 # bash deploy.sh stop 停止 # bash deploy.sh logs 查看日志 # bash deploy.sh status 容器状态 # bash deploy.sh config 检查配置 # bash deploy.sh init-nacos 初始化 Nacos 配置(仅 Cloud 模式) # ============================================ set -e DIR="$(cd "$(dirname "$0")" && pwd)" cd "$DIR" PROJECT_DIR="$(dirname "$DIR")" CMD="${1:-help}" BRANCH="${2:-master}" # ============================================ # 工具函数 # ============================================ # 生成随机 JWT 密钥 gen_jwt_secret() { LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 48 } # TCP 端口连通性检查 check_port() { local host="$1" port="$2" label="$3" echo -n " ${label} (${host}:${port}) ... " if command -v nc &>/dev/null; then if nc -z -w 5 "$host" "$port" 2>/dev/null; then echo "✅ 可达" return 0 fi elif command -v timeout &>/dev/null; then if timeout 5 bash -c "echo >/dev/tcp/${host}/${port}" 2>/dev/null; then echo "✅ 可达" return 0 fi else # macOS 没有 timeout,用 perl 做超时 if perl -e "use IO::Socket::INET; \$s=IO::Socket::INET->new(PeerAddr=>'$host',PeerPort=>$port,Timeout=>5); exit(1) unless \$s;" 2>/dev/null; then echo "✅ 可达" return 0 fi fi echo "❌ 不可达" return 1 } # MySQL 连接 + 库存在测试 check_mysql_connect_and_db() { local host="$1" port="$2" user="$3" pass="$4" db_name="$5" echo -n " MySQL 连接测试 (${user}@${host}:${port}/${db_name}) ... " # 确定用哪个 mysql 客户端 local mysql_cmd="" local connect_host="${host}" if command -v mysql &>/dev/null; then mysql_cmd="mysql -h${host} -P${port} -u${user} -p${pass}" else local docker_opts="--network host" if [ "${host}" = "127.0.0.1" ] || [ "${host}" = "localhost" ]; then if ! docker info 2>/dev/null | grep -q "OSType: linux"; then connect_host="host.docker.internal" docker_opts="" fi fi mysql_cmd="docker run --rm ${docker_opts} mysql:8.0 mysql -h${connect_host} -P${port} -u${user} -p${pass}" fi # 第一步:基础连接测试 if ! $mysql_cmd -e "SELECT 1" 2>/dev/null >/dev/null; then echo "❌ 连接失败(用户名/密码/网络)" return 1 fi # 第二步:库存在测试 if $mysql_cmd -e "USE ${db_name}" 2>/dev/null >/dev/null; then echo "✅ 连接成功,库 '${db_name}' 存在" return 0 fi echo "⚠️ 连接成功,但库 '${db_name}' 不存在" return 2 } # Redis 连接测试 check_redis_connect() { local host="$1" port="$2" pass="$3" echo -n " Redis 连接测试 (${host}:${port}) ... " local redis_cmd="" local connect_host="${host}" if command -v redis-cli &>/dev/null; then redis_cmd="redis-cli -h ${host} -p ${port}" else local docker_opts="--network host" if [ "${host}" = "127.0.0.1" ] || [ "${host}" = "localhost" ]; then if ! docker info 2>/dev/null | grep -q "OSType: linux"; then connect_host="host.docker.internal" docker_opts="" fi fi redis_cmd="docker run --rm ${docker_opts} redis:7-alpine redis-cli -h ${connect_host} -p ${port}" fi if [ -n "$pass" ]; then ${redis_cmd} -a "${pass}" --no-auth-warning PING 2>/dev/null | grep -q "PONG" && { echo "✅ 连接成功"; return 0 } else ${redis_cmd} PING 2>/dev/null | grep -q "PONG" && { echo "✅ 连接成功"; return 0 } fi echo "❌ 连接失败" return 1 } # MinIO 连接测试 check_minio_connect() { local host="$1" port="$2" access_key="$3" secret_key="$4" echo -n " MinIO 连接测试 (${host}:${port}) ... " if curl -s -o /dev/null -w "%{http_code}" "http://${host}:${port}/minio/health/live" 2>/dev/null | grep -q "200"; then echo "✅ 可达" return 0 fi echo "❌ 不可达" return 1 } # 根据 MODE 变量检查 external 服务连通性 check_connectivity() { local errors=0 echo "" echo ">>> 检查外接服务连通性..." if [ "${MYSQL_MODE}" = "external" ]; then check_port "${MYSQL_HOST}" "${MYSQL_PORT}" "MySQL" || errors=$((errors + 1)) fi if [ "${REDIS_MODE}" = "external" ]; then check_port "${REDIS_HOST}" "${REDIS_PORT}" "Redis" || errors=$((errors + 1)) fi if [ "${MINIO_MODE}" = "external" ]; then check_port "${MINIO_HOST}" "${MINIO_PORT}" "MinIO" || errors=$((errors + 1)) fi return $errors } # 数据库初始化(通过 mysql 客户端执行 SQL) do_init_db() { load_env if [ "${MYSQL_MODE}" != "external" ]; then echo "ℹ️ MYSQL_MODE=${MYSQL_MODE},使用 Docker MySQL,容器首次启动时自动初始化,无需手动执行。" return 0 fi if [ "${DB_INIT}" != "true" ]; then echo "ℹ️ DB_INIT=false,跳过数据库初始化。" echo " 如需手动初始化:" echo " 1. 在 .env 中设置 DB_INIT=true" echo " 2. 将 ../db/ghb_base_init.sql 导入 ${MYSQL_HOST}:${MYSQL_PORT}/${DB_NAME}" return 0 fi local sql_file="${DIR}/../db/ghb_base_init.sql" if [ ! -f "$sql_file" ]; then echo "❌ SQL 文件不存在: $sql_file" exit 1 fi echo "" echo "=========================================" echo " 数据库初始化" echo "=========================================" echo " 目标: ${MYSQL_HOST}:${MYSQL_PORT}/${DB_NAME}" echo " SQL: ${sql_file}" echo "=========================================" echo "" echo ">>> 检查 MySQL 连通性..." if ! check_port "${MYSQL_HOST}" "${MYSQL_PORT}" "MySQL"; then echo "❌ MySQL 不可达,无法初始化数据库" exit 1 fi echo ">>> 正在执行建表脚本..." # 优先使用宿主机 mysql 客户端 if command -v mysql &>/dev/null; then mysql -h"${MYSQL_HOST}" -P"${MYSQL_PORT}" -u"${MYSQL_USER}" -p"${MYSQL_PASS}" "${DB_NAME}" < "$sql_file" 2>&1 local rc=$? else # 通过 Docker 运行 mysql 客户端,兼容 Mac(host.docker.internal)和 Linux(--network host) local connect_host="${MYSQL_HOST}" local network_mode="--network host" if [ "${MYSQL_HOST}" = "127.0.0.1" ] || [ "${MYSQL_HOST}" = "localhost" ]; then # Docker Desktop Mac/Windows 用 host.docker.internal if docker info 2>/dev/null | grep -q "OSType: linux"; then network_mode="--network host" else connect_host="host.docker.internal" network_mode="" fi fi cat "$sql_file" | docker run --rm -i $network_mode \ -v "$(dirname "$sql_file"):/sql:ro" \ mysql:8.0 \ mysql -h"${connect_host}" -P"${MYSQL_PORT}" -u"${MYSQL_USER}" -p"${MYSQL_PASS}" "${DB_NAME}" 2>&1 local rc=$? fi if [ $rc -eq 0 ]; then echo "" echo "✅ 数据库初始化完成" echo "" echo ">>> 为防止重复初始化,请在 .env 中设置 DB_INIT=false" # 自动关闭初始化开关,防止重复执行 if [ -f "${DIR}/.env" ]; then sed -i.bak 's/^DB_INIT=true$/DB_INIT=false/' "${DIR}/.env" && rm -f "${DIR}/.env.bak" echo " 已自动设置 DB_INIT=false" fi else echo "" echo "❌ 数据库初始化失败" exit 1 fi } # 检查 .env 是否存在 check_env() { if [ ! -f "${DIR}/.env" ]; then echo "❌ 未找到 .env,请先执行:bash deploy.sh init" exit 1 fi } # 加载 .env load_env() { check_env set -a source "${DIR}/.env" set +a } # 获取 compose 命令(根据 MODE 变量自动附加 --profile) get_compose_cmd() { local compose_file="${COMPOSE_FILE:-docker-compose.yml}" local cmd="docker compose -f ${DIR}/${compose_file}" local profiles="" [ "${MYSQL_MODE:-docker}" = "docker" ] && profiles="${profiles},mysql" [ "${REDIS_MODE:-docker}" = "docker" ] && profiles="${profiles},redis" [ "${MINIO_MODE:-none}" = "docker" ] && profiles="${profiles},minio" [ "${FRONTEND_ENABLED:-false}" = "true" ] && profiles="${profiles},frontend" profiles="${profiles#,}" # 去掉开头的逗号 if [ -n "$profiles" ]; then for p in ${profiles//,/ }; do cmd="${cmd} --profile $p" done fi echo "$cmd" } # 等待服务健康 wait_for_healthy() { local container="$1" label="$2" max_wait="${3:-60}" echo -n ">>> 等待 ${label} 就绪..." for i in $(seq 1 "$max_wait"); do local status status=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "unknown") if [ "$status" = "healthy" ]; then echo " ✅" return 0 fi sleep 2 done echo " ❌ 超时" return 1 } # Docker MySQL 启动后验证库是否存在 verify_mysql_db() { local container="${APP_NAME:-test}-mysql" local db_name="${DB_NAME:-ghb_base}" local pass="${MYSQL_ROOT_PASSWORD:-ChangeMe123}" echo -n ">>> 验证数据库 '${db_name}' ... " if docker exec "$container" mysql -uroot -p"${pass}" -e "USE ${db_name}" 2>/dev/null >/dev/null; then echo "✅ 已就绪" return 0 else echo "⚠️ 库 '${db_name}' 不存在,请检查 MySQL 初始化" return 1 fi } # Docker Redis 启动后验证连通性 verify_redis() { local container="${APP_NAME:-test}-redis" local pass="${REDIS_PASS:-}" echo -n ">>> 验证 Redis 连接 ... " if [ -n "$pass" ]; then docker exec "$container" redis-cli -a "${pass}" --no-auth-warning PING 2>/dev/null | grep -q "PONG" else docker exec "$container" redis-cli PING 2>/dev/null | grep -q "PONG" fi if [ $? -eq 0 ]; then echo "✅ 已就绪" return 0 else echo "⚠️ Redis 未就绪" return 1 fi } # ============================================ # init — 交互式初始化 .env # ============================================ do_init() { echo "" echo "=========================================" echo " ghb-base 部署配置向导" echo "=========================================" echo "" # === 一级选择:部署架构 === echo "请选择部署架构:" echo " 1. 单体" echo " 2. Spring Cloud" echo "" read -r -p "请输入 1/2 [1]: " DEPLOY_MODE DEPLOY_MODE="${DEPLOY_MODE:-1}" case "$DEPLOY_MODE" in 1) COMPOSE_FILE="docker-compose.yml" MODE_NAME="单体" ;; 2) COMPOSE_FILE="docker-compose.cloud.yml" MODE_NAME="Spring Cloud" DEPLOY_MODE=3 ;; *) echo "❌ 无效选择,请输入 1/2" exit 1 ;; esac echo "" echo ">>> 已选择: ${MODE_NAME}" echo "" # === 应用配置(DB_NAME 需在 MySQL 测试前获取) === echo "--- 应用配置 ---" read -r -p "应用名称 [ghb]: " APP_NAME APP_NAME="${APP_NAME:-test}" read -r -p "应用端口 [8008]: " APP_PORT APP_PORT="${APP_PORT:-8008}" read -r -p "数据库名 [ghb_base]: " DB_NAME DB_NAME="${DB_NAME:-ghb_base}" JWT_SECRET=$(gen_jwt_secret) echo "" # === 二级选择:MySQL === echo "--- MySQL ---" echo " 1. Docker 一起安装" echo " 2. 连接外部服务" echo "" read -r -p "请选择 1/2 [1]: " MYSQL_MODE_CHOICE MYSQL_MODE_CHOICE="${MYSQL_MODE_CHOICE:-1}" case "$MYSQL_MODE_CHOICE" in 1) MYSQL_MODE="docker" read -r -p "MySQL 端口 [3306]: " MYSQL_PORT MYSQL_PORT="${MYSQL_PORT:-3306}" read -r -s -p "MySQL root 密码 [ChangeMe123]: " MYSQL_ROOT_PASSWORD echo "" MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-ChangeMe123}" MYSQL_HOST="mysql" MYSQL_USER="root" MYSQL_PASS="${MYSQL_ROOT_PASSWORD}" ;; 2) MYSQL_MODE="external" echo "请输入外部 MySQL 连接信息:" read -r -p " 主机地址 [127.0.0.1]: " MYSQL_HOST MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}" read -r -p " 端口 [3306]: " MYSQL_PORT MYSQL_PORT="${MYSQL_PORT:-3306}" read -r -p " 用户名 [root]: " MYSQL_USER MYSQL_USER="${MYSQL_USER:-root}" read -r -s -p " 密码: " MYSQL_PASS echo "" echo "" echo ">>> 测试 MySQL 连接..." check_mysql_connect_and_db "${MYSQL_HOST}" "${MYSQL_PORT}" "${MYSQL_USER}" "${MYSQL_PASS}" "${DB_NAME}" local mysql_test_rc=$? if [ $mysql_test_rc -eq 2 ]; then # 连接成功但库不存在 echo "" echo "⚠️ MySQL 连接成功,但数据库 '${DB_NAME}' 不存在!" echo " 请先创建数据库:" echo " CREATE DATABASE ${DB_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" echo "" read -r -p "是否仍然继续?[y/N]: " continue_anyway if [ "${continue_anyway}" != "y" ] && [ "${continue_anyway}" != "Y" ]; then echo "❌ 已取消" exit 1 fi elif [ $mysql_test_rc -ne 0 ]; then echo "" echo "⚠️ MySQL 连接失败!请检查:" echo " 1. MySQL 服务是否已启动" echo " 2. 主机/端口/用户名/密码是否正确" echo " 3. 防火墙是否放行 ${MYSQL_PORT} 端口" echo "" read -r -p "是否仍然继续?[y/N]: " continue_anyway if [ "${continue_anyway}" != "y" ] && [ "${continue_anyway}" != "Y" ]; then echo "❌ 已取消" exit 1 fi fi ;; *) echo "❌ 无效选择" exit 1 ;; esac echo "" # === 二级选择:Redis === echo "--- Redis ---" echo " 1. Docker 一起安装" echo " 2. 连接外部服务" echo "" read -r -p "请选择 1/2 [1]: " REDIS_MODE_CHOICE REDIS_MODE_CHOICE="${REDIS_MODE_CHOICE:-1}" case "$REDIS_MODE_CHOICE" in 1) REDIS_MODE="docker" read -r -p "Redis 端口 [6379]: " REDIS_HOST_PORT REDIS_HOST_PORT="${REDIS_HOST_PORT:-6379}" read -r -s -p "Redis 密码(无密码直接回车): " REDIS_PASS echo "" REDIS_HOST="redis" REDIS_PORT="6379" # 容器内部固定 6379,App 用此变量连接 REDIS_DB="0" ;; 2) REDIS_MODE="external" echo "请输入外部 Redis 连接信息:" read -r -p " 主机地址 [127.0.0.1]: " REDIS_HOST REDIS_HOST="${REDIS_HOST:-127.0.0.1}" read -r -p " 端口 [6379]: " REDIS_PORT REDIS_PORT="${REDIS_PORT:-6379}" read -r -s -p " 密码(无密码直接回车): " REDIS_PASS echo "" read -r -p " DB 编号 [0]: " REDIS_DB REDIS_DB="${REDIS_DB:-0}" echo "" echo ">>> 测试 Redis 连接..." check_redis_connect "${REDIS_HOST}" "${REDIS_PORT}" "${REDIS_PASS}" || { echo "" echo "⚠️ Redis 连接失败!请检查:" echo " 1. Redis 服务是否已启动" echo " 2. 主机/端口/密码是否正确" echo "" read -r -p "是否仍然继续?[y/N]: " continue_anyway if [ "${continue_anyway}" != "y" ] && [ "${continue_anyway}" != "Y" ]; then echo "❌ 已取消" exit 1 fi } ;; *) echo "❌ 无效选择" exit 1 ;; esac echo "" # === 二级选择:MinIO === echo "--- MinIO ---" echo " 1. Docker 一起安装" echo " 2. 连接外部服务" echo " 3. 不使用" echo "" read -r -p "请选择 1/2/3 [3]: " MINIO_MODE_CHOICE MINIO_MODE_CHOICE="${MINIO_MODE_CHOICE:-3}" case "$MINIO_MODE_CHOICE" in 1) MINIO_MODE="docker" MINIO_HOST="minio" read -r -p "MinIO API 端口 [9000]: " MINIO_PORT MINIO_PORT="${MINIO_PORT:-9000}" read -r -p "MinIO Console 端口 [9001]: " MINIO_CONSOLE_PORT MINIO_CONSOLE_PORT="${MINIO_CONSOLE_PORT:-9001}" read -r -p "MinIO Access Key [minioadmin]: " MINIO_ACCESS_KEY MINIO_ACCESS_KEY="${MINIO_ACCESS_KEY:-minioadmin}" while [ "${#MINIO_ACCESS_KEY}" -lt 3 ]; do echo "❌ MinIO Access Key 长度至少为 3 位" read -r -p "MinIO Access Key [minioadmin]: " MINIO_ACCESS_KEY MINIO_ACCESS_KEY="${MINIO_ACCESS_KEY:-minioadmin}" done read -r -s -p "MinIO Secret Key [minioadmin]: " MINIO_SECRET_KEY echo "" MINIO_SECRET_KEY="${MINIO_SECRET_KEY:-minioadmin}" while [ "${#MINIO_SECRET_KEY}" -lt 8 ]; do echo "❌ MinIO Secret Key 长度至少为 8 位" read -r -s -p "MinIO Secret Key: " MINIO_SECRET_KEY echo "" MINIO_SECRET_KEY="${MINIO_SECRET_KEY:-minioadmin}" done read -r -p "MinIO Bucket 名称 [ghb-base]: " MINIO_BUCKET MINIO_BUCKET="${MINIO_BUCKET:-ghb-base}" MINIO_ENDPOINT="http://minio:9000" ;; 2) MINIO_MODE="external" echo "请输入外部 MinIO 连接信息:" read -r -p " 主机地址 [127.0.0.1]: " MINIO_HOST MINIO_HOST="${MINIO_HOST:-127.0.0.1}" read -r -p " API 端口 [9000]: " MINIO_PORT MINIO_PORT="${MINIO_PORT:-9000}" read -r -p " Access Key [minioadmin]: " MINIO_ACCESS_KEY MINIO_ACCESS_KEY="${MINIO_ACCESS_KEY:-minioadmin}" read -r -s -p " Secret Key [minioadmin]: " MINIO_SECRET_KEY echo "" MINIO_SECRET_KEY="${MINIO_SECRET_KEY:-minioadmin}" read -r -p " Bucket 名称 [ghb-base]: " MINIO_BUCKET MINIO_BUCKET="${MINIO_BUCKET:-ghb-base}" MINIO_ENDPOINT="http://${MINIO_HOST}:${MINIO_PORT}" echo "" echo ">>> 测试 MinIO 连接..." check_minio_connect "${MINIO_HOST}" "${MINIO_PORT}" "${MINIO_ACCESS_KEY}" "${MINIO_SECRET_KEY}" || { echo "" echo "⚠️ MinIO 不可达!请检查:" echo " 1. MinIO 服务是否已启动" echo " 2. 主机/端口是否正确" echo "" read -r -p "是否仍然继续?[y/N]: " continue_anyway if [ "${continue_anyway}" != "y" ] && [ "${continue_anyway}" != "Y" ]; then echo "❌ 已取消" exit 1 fi } ;; 3) MINIO_MODE="none" MINIO_HOST="127.0.0.1" MINIO_PORT="9000" MINIO_ACCESS_KEY="minioadmin" MINIO_SECRET_KEY="minioadmin" MINIO_BUCKET="ghb-base" MINIO_ENDPOINT="http://127.0.0.1:9000" ;; *) echo "❌ 无效选择" exit 1 ;; esac echo "" # 自动拼接 JDBC URL if [ "$MYSQL_MODE" = "docker" ]; then MYSQL_URL="jdbc:mysql://mysql:3306/${DB_NAME}?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" else MYSQL_URL="jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${DB_NAME}?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" fi # 网络配置 echo "" echo "--- 网络配置 ---" read -r -p "Docker 内部网络名 [ghb-net]: " NETWORK_NAME NETWORK_NAME="${NETWORK_NAME:-ghb-net}" # SpringCloud 额外配置 if [ "$DEPLOY_MODE" = "3" ]; then echo "" echo "--- Spring Cloud 额外配置 ---" read -r -p "Nacos 端口 [8848]: " NACOS_PORT NACOS_PORT="${NACOS_PORT:-8848}" read -r -p "Gateway 端口 [9999]: " GATEWAY_PORT GATEWAY_PORT="${GATEWAY_PORT:-9999}" read -r -p "System 服务端口 [7001]: " SYSTEM_PORT SYSTEM_PORT="${SYSTEM_PORT:-7001}" fi # 数据库初始化(external MySQL 时) DB_INIT="false" if [ "$MYSQL_MODE" = "external" ]; then echo "" read -r -p "外接数据库是否已建表?[Y/n]: " DB_READY if [ "${DB_READY}" != "Y" ] && [ "${DB_READY}" != "y" ] && [ -n "${DB_READY}" ]; then echo " 如果数据库是空的,可以自动执行建表脚本(${PROJECT_DIR}/db/ghb_base_init.sql)" read -r -p " 是否自动初始化数据库表?[y/N]: " DO_INIT_DB if [ "${DO_INIT_DB}" = "y" ] || [ "${DO_INIT_DB}" = "Y" ]; then DB_INIT="true" fi fi fi # 前端集成 FRONTEND_ENABLED="false" echo "" read -r -p "是否集成前端(需要先 cd ghb-frontend && pnpm build 生成 dist)?[y/N]: " ENABLE_FRONTEND if [ "${ENABLE_FRONTEND}" = "y" ] || [ "${ENABLE_FRONTEND}" = "Y" ]; then FRONTEND_ENABLED="true" read -r -p "前端端口 [80]: " FRONTEND_PORT FRONTEND_PORT="${FRONTEND_PORT:-80}" fi # === 生成 .env 文件 === echo "" echo ">>> 正在生成 .env 文件..." cat > "${DIR}/.env" << EOF # ============================================ # ghb-base 部署配置 # 生成时间: $(date '+%Y-%m-%d %H:%M:%S') # 部署架构: ${MODE_NAME} # ============================================ DEPLOY_MODE=${DEPLOY_MODE} COMPOSE_FILE=${COMPOSE_FILE} # 应用 APP_NAME=${APP_NAME} APP_PORT=${APP_PORT} JAVA_OPTS="-Xms512m -Xmx512m" # 数据库 DB_NAME=${DB_NAME} # 网络 NETWORK_NAME=${NETWORK_NAME} # ---- 中间件策略 ---- # docker = Docker 一起安装 | external = 连接外部服务 | none = 不使用(仅 MinIO) MYSQL_MODE=${MYSQL_MODE} REDIS_MODE=${REDIS_MODE} MINIO_MODE=${MINIO_MODE} EOF # MySQL 配置 if [ "$MYSQL_MODE" = "docker" ]; then cat >> "${DIR}/.env" << EOF # ---- MySQL(Docker) ---- MYSQL_PORT=${MYSQL_PORT} MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} MYSQL_HOST=${MYSQL_HOST} MYSQL_USER=${MYSQL_USER} MYSQL_PASS='${MYSQL_PASS}' MYSQL_URL='${MYSQL_URL}' EOF else cat >> "${DIR}/.env" << EOF # ---- MySQL(外部) ---- MYSQL_HOST=${MYSQL_HOST} MYSQL_PORT=${MYSQL_PORT} MYSQL_USER=${MYSQL_USER} MYSQL_PASS='${MYSQL_PASS}' MYSQL_URL='${MYSQL_URL}' EOF fi # Redis 配置 if [ "$REDIS_MODE" = "docker" ]; then cat >> "${DIR}/.env" << EOF # ---- Redis(Docker) ---- REDIS_PORT=${REDIS_PORT} REDIS_HOST_PORT=${REDIS_HOST_PORT} REDIS_PASS='${REDIS_PASS}' REDIS_HOST=${REDIS_HOST} REDIS_DB=${REDIS_DB} EOF else cat >> "${DIR}/.env" << EOF # ---- Redis(外部) ---- REDIS_HOST=${REDIS_HOST} REDIS_PORT=${REDIS_PORT} REDIS_PASS='${REDIS_PASS}' REDIS_DB=${REDIS_DB} EOF fi # MinIO 配置 if [ "$MINIO_MODE" = "docker" ]; then cat >> "${DIR}/.env" << EOF # ---- MinIO(Docker) ---- MINIO_HOST=${MINIO_HOST} MINIO_PORT=${MINIO_PORT} MINIO_CONSOLE_PORT=${MINIO_CONSOLE_PORT} MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY} MINIO_SECRET_KEY=${MINIO_SECRET_KEY} MINIO_BUCKET=${MINIO_BUCKET} MINIO_ENDPOINT=${MINIO_ENDPOINT} EOF elif [ "$MINIO_MODE" = "external" ]; then cat >> "${DIR}/.env" << EOF # ---- MinIO(外部) ---- MINIO_HOST=${MINIO_HOST} MINIO_PORT=${MINIO_PORT} MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY} MINIO_SECRET_KEY=${MINIO_SECRET_KEY} MINIO_BUCKET=${MINIO_BUCKET} MINIO_ENDPOINT=${MINIO_ENDPOINT} EOF else cat >> "${DIR}/.env" << EOF # ---- MinIO(不使用) ---- MINIO_HOST=127.0.0.1 MINIO_PORT=9000 MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_BUCKET=ghb-base MINIO_ENDPOINT=http://127.0.0.1:9000 EOF fi # SpringCloud 额外配置 if [ "$DEPLOY_MODE" = "3" ]; then cat >> "${DIR}/.env" << EOF # ---- Spring Cloud ---- NACOS_PORT=${NACOS_PORT} GATEWAY_PORT=${GATEWAY_PORT} SYSTEM_PORT=${SYSTEM_PORT} EOF fi # 数据库初始化 cat >> "${DIR}/.env" << EOF # 数据库初始化(true 时 start/rebuild 会自动建表) DB_INIT=${DB_INIT} EOF # JWT cat >> "${DIR}/.env" << EOF # JWT JWT_SECRET='${JWT_SECRET}' EOF # 前端 cat >> "${DIR}/.env" << EOF # 前端集成 FRONTEND_ENABLED=${FRONTEND_ENABLED} EOF if [ "${FRONTEND_ENABLED}" = "true" ]; then echo "FRONTEND_PORT=${FRONTEND_PORT}" >> "${DIR}/.env" fi # 上传方式 cat >> "${DIR}/.env" << EOF # 上传方式: local(本地) / minio(MinIO) JEECG_UPLOAD_TYPE=local # 跳过连通性检查(true 时不检查外接服务是否可达) SKIP_CONNECTIVITY_CHECK=false EOF # === 输出摘要 === echo "" echo "=========================================" echo " ✅ .env 文件已生成" echo "=========================================" echo "" echo "部署架构: ${MODE_NAME}" echo "MySQL: ${MYSQL_MODE}" echo "Redis: ${REDIS_MODE}" echo "MinIO: ${MINIO_MODE}" if [ "${FRONTEND_ENABLED}" = "true" ]; then echo "前端: 已集成(端口 ${FRONTEND_PORT:-80})" echo " 注意:需要先 cd ghb-frontend && pnpm build 生成 dist/" fi echo "" echo "下一步:" echo " bash deploy.sh build # 编译镜像" echo " bash deploy.sh start # 启动服务" if [ "$DEPLOY_MODE" = "3" ]; then echo " bash deploy.sh init-nacos # 初始化 Nacos 配置" fi echo "" } # ============================================ # start — 启动 # ============================================ do_start() { load_env local COMPOSE_CMD COMPOSE_CMD=$(get_compose_cmd) local COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" echo ">>> 部署架构: ${DEPLOY_MODE:-1}" echo ">>> MySQL: ${MYSQL_MODE:-docker} Redis: ${REDIS_MODE:-docker} MinIO: ${MINIO_MODE:-none}" # 检查 external 服务连通性 if [ "${SKIP_CONNECTIVITY_CHECK:-false}" != "true" ]; then check_connectivity || { echo "" echo "⚠️ 部分外接服务不可达,部署可能失败。" echo "" echo " 请选择:" echo " 1. 退出,先修复连接问题" echo " 2. 强制部署(跳过检查)" echo "" read -r -p " 请输入 1/2 [1]: " choice case "${choice:-1}" in 2) echo ">>> 跳过检查,强制部署..." ;; *) echo "❌ 已取消"; exit 1 ;; esac } fi echo ">>> 停掉旧容器..." $COMPOSE_CMD down 2>/dev/null || true echo "" if [ "${DEPLOY_MODE}" = "3" ]; then # ============================================ # Cloud 模式:分阶段启动 # Phase 1: docker 中间件 → Nacos # Phase 2: 推送配置 → 业务服务 # ============================================ echo ">>> [Phase 1] 启动中间件和 Nacos..." # 按需启动 docker 中间件 if [ "${MYSQL_MODE:-docker}" = "docker" ]; then echo " 启动 MySQL..." docker compose -f "${DIR}/${COMPOSE_FILE}" --profile mysql up -d --force-recreate mysql wait_for_healthy "${APP_NAME:-test}-mysql" "MySQL" 60 || { echo "❌ MySQL 启动失败" exit 1 } verify_mysql_db fi if [ "${REDIS_MODE:-docker}" = "docker" ]; then echo " 启动 Redis..." docker compose -f "${DIR}/${COMPOSE_FILE}" --profile redis up -d --force-recreate redis wait_for_healthy "${APP_NAME:-test}-redis" "Redis" 30 || { echo "❌ Redis 启动失败" exit 1 } verify_redis fi # 启动 Nacos echo " 启动 Nacos..." docker compose -f "${DIR}/${COMPOSE_FILE}" up -d --force-recreate nacos # 等待 Nacos 健康 echo ">>> 等待 Nacos 就绪..." local NACOS_HEALTH_URL="http://localhost:${NACOS_PORT:-8848}/nacos/v1/console/health/liveness" for i in $(seq 1 60); do if curl -s "$NACOS_HEALTH_URL" 2>/dev/null | grep -q "OK"; then echo " ✅ Nacos 已就绪" break fi if [ "$i" -eq 60 ]; then echo " ❌ Nacos 启动超时,请检查日志: docker logs ${APP_NAME:-test}-nacos" exit 1 fi sleep 3 done # 首次部署:Nacos 无配置则自动导入 echo ">>> 检查 Nacos 配置..." local CHECK_RESP CHECK_RESP=$(curl -s "http://localhost:${NACOS_PORT:-8848}/nacos/v1/cs/configs?dataId=jeecg.yaml&group=DEFAULT_GROUP") if [ -z "$CHECK_RESP" ] || [ "$CHECK_RESP" = "config data not exist" ]; then echo " 首次部署,自动导入配置..." NACOS_URL="http://localhost:${NACOS_PORT:-8848}" bash "${DIR}/init-nacos.sh" else echo " ✅ Nacos 配置已存在,跳过导入" fi # Phase 2: 启动业务服务 echo "" echo ">>> [Phase 2] 启动业务服务..." $COMPOSE_CMD up -d --force-recreate echo "" echo "✅ 已启动" echo " Nacos: http://localhost:${NACOS_PORT:-8848}/nacos" echo " Gateway: http://localhost:${GATEWAY_PORT:-9999}" echo " System: http://localhost:${SYSTEM_PORT:-7001}" if [ "${MINIO_MODE:-none}" = "docker" ]; then echo " MinIO: http://localhost:${MINIO_PORT:-9000} (Console: ${MINIO_CONSOLE_PORT:-9001})" fi if [ "${FRONTEND_ENABLED:-false}" = "true" ]; then echo " 前端: http://localhost:${FRONTEND_PORT:-80}" fi else # ============================================ # 单体模式:按需分步启动 # ============================================ # 先启动 docker 中间件 if [ "${MYSQL_MODE:-docker}" = "docker" ]; then echo ">>> 启动 MySQL..." docker compose -f "${DIR}/${COMPOSE_FILE}" --profile mysql up -d --force-recreate mysql wait_for_healthy "${APP_NAME:-test}-mysql" "MySQL" 60 || { echo "❌ MySQL 启动失败" exit 1 } verify_mysql_db fi if [ "${REDIS_MODE:-docker}" = "docker" ]; then echo ">>> 启动 Redis..." docker compose -f "${DIR}/${COMPOSE_FILE}" --profile redis up -d --force-recreate redis wait_for_healthy "${APP_NAME:-test}-redis" "Redis" 30 || { echo "❌ Redis 启动失败" exit 1 } verify_redis fi if [ "${MINIO_MODE:-none}" = "docker" ]; then echo ">>> 启动 MinIO..." docker compose -f "${DIR}/${COMPOSE_FILE}" --profile minio up -d --force-recreate minio fi # 启动 app(及其它 profiled 服务如 frontend) echo ">>> 启动应用..." $COMPOSE_CMD up -d --force-recreate echo "" echo "✅ 已启动" echo " 地址: http://localhost:${APP_PORT}/ghb" if [ "${MINIO_MODE:-none}" = "docker" ]; then echo " MinIO: http://localhost:${MINIO_PORT:-9000} (Console: ${MINIO_CONSOLE_PORT:-9001})" fi if [ "${FRONTEND_ENABLED:-false}" = "true" ]; then echo " 前端: http://localhost:${FRONTEND_PORT:-80}" fi fi echo " 日志: bash deploy.sh logs" } # ============================================ # build — 编译镜像 # ============================================ do_build() { load_env local COMPOSE_CMD COMPOSE_CMD=$(get_compose_cmd) echo ">>> 部署架构: ${DEPLOY_MODE:-1}" echo ">>> 编译镜像..." if [ "${DEPLOY_MODE}" = "3" ]; then echo " 编译所有 Cloud 服务(首次较慢,约 10-20 分钟)..." $COMPOSE_CMD build else $COMPOSE_CMD build app fi echo "" echo "✅ 编译完成,执行 bash deploy.sh start 启动" } # ============================================ # rebuild — 拉取代码 + 编译 + 启动 # ============================================ do_rebuild() { load_env local COMPOSE_CMD COMPOSE_CMD=$(get_compose_cmd) local COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" echo ">>> 部署架构: ${DEPLOY_MODE:-1}" echo ">>> 切到分支 ${BRANCH} 并拉取最新代码..." # 拉代码前检查 external 服务连通性 if [ "${SKIP_CONNECTIVITY_CHECK:-false}" != "true" ]; then check_connectivity || { echo "" echo "⚠️ 部分外接服务不可达,部署可能失败。" read -r -p " 是否强制继续?[y/N]: " force if [ "${force}" != "y" ] && [ "${force}" != "Y" ]; then echo "❌ 已取消" exit 1 fi echo ">>> 跳过检查,强制继续..." } fi if [ "${DEPLOY_MODE}" = "3" ]; then # Cloud 模式:需要拉取完整仓库(ghb-base 根目录) local REPO_DIR="$(dirname "$PROJECT_DIR")" cd "$REPO_DIR" else cd "$PROJECT_DIR" fi git fetch origin git checkout "${BRANCH}" git pull origin "${BRANCH}" cd "$DIR" echo "" echo ">>> 重新编译..." if [ "${DEPLOY_MODE}" = "3" ]; then echo " 编译所有 Cloud 服务(首次较慢,约 10-20 分钟)..." $COMPOSE_CMD build --no-cache else $COMPOSE_CMD build --no-cache app fi echo "" if [ "${DEPLOY_MODE}" = "3" ]; then # Cloud 模式:分阶段启动 echo ">>> [Phase 1] 启动中间件和 Nacos..." $COMPOSE_CMD down 2>/dev/null || true if [ "${MYSQL_MODE:-docker}" = "docker" ]; then echo " 启动 MySQL..." docker compose -f "${DIR}/${COMPOSE_FILE}" --profile mysql up -d --force-recreate mysql wait_for_healthy "${APP_NAME:-test}-mysql" "MySQL" 60 || { echo "❌ MySQL 启动失败" exit 1 } verify_mysql_db fi if [ "${REDIS_MODE:-docker}" = "docker" ]; then echo " 启动 Redis..." docker compose -f "${DIR}/${COMPOSE_FILE}" --profile redis up -d --force-recreate redis wait_for_healthy "${APP_NAME:-test}-redis" "Redis" 30 || { echo "❌ Redis 启动失败" exit 1 } verify_redis fi echo " 启动 Nacos..." docker compose -f "${DIR}/${COMPOSE_FILE}" up -d --force-recreate nacos echo ">>> 等待 Nacos 就绪..." local NACOS_HEALTH_URL="http://localhost:${NACOS_PORT:-8848}/nacos/v1/console/health/liveness" for i in $(seq 1 60); do if curl -s "$NACOS_HEALTH_URL" 2>/dev/null | grep -q "OK"; then echo " ✅ Nacos 已就绪" break fi if [ "$i" -eq 60 ]; then echo " ❌ Nacos 启动超时" exit 1 fi sleep 3 done # 首次部署:Nacos 无配置则自动导入 echo ">>> 检查 Nacos 配置..." local CHECK_RESP CHECK_RESP=$(curl -s "http://localhost:${NACOS_PORT:-8848}/nacos/v1/cs/configs?dataId=jeecg.yaml&group=DEFAULT_GROUP") if [ -z "$CHECK_RESP" ] || [ "$CHECK_RESP" = "config data not exist" ]; then echo " 首次部署,自动导入配置..." NACOS_URL="http://localhost:${NACOS_PORT:-8848}" bash "${DIR}/init-nacos.sh" else echo " ✅ Nacos 配置已存在,跳过导入" fi echo "" echo ">>> [Phase 2] 启动业务服务..." $COMPOSE_CMD up -d --force-recreate echo "" echo "✅ 重新部署完成(分支: ${BRANCH})" else # 单体模式:按需分步启动 $COMPOSE_CMD down 2>/dev/null || true if [ "${MYSQL_MODE:-docker}" = "docker" ]; then echo ">>> 启动 MySQL..." docker compose -f "${DIR}/${COMPOSE_FILE}" --profile mysql up -d --force-recreate mysql wait_for_healthy "${APP_NAME:-test}-mysql" "MySQL" 60 || { echo "❌ MySQL 启动失败" exit 1 } verify_mysql_db fi if [ "${REDIS_MODE:-docker}" = "docker" ]; then echo ">>> 启动 Redis..." docker compose -f "${DIR}/${COMPOSE_FILE}" --profile redis up -d --force-recreate redis wait_for_healthy "${APP_NAME:-test}-redis" "Redis" 30 || { echo "❌ Redis 启动失败" exit 1 } verify_redis fi if [ "${MINIO_MODE:-none}" = "docker" ]; then echo ">>> 启动 MinIO..." docker compose -f "${DIR}/${COMPOSE_FILE}" --profile minio up -d --force-recreate minio fi echo ">>> 启动应用..." $COMPOSE_CMD up -d --force-recreate echo "" echo "✅ 重新部署完成(分支: ${BRANCH})" fi } # ============================================ # stop — 停止 # ============================================ do_stop() { load_env local COMPOSE_CMD COMPOSE_CMD=$(get_compose_cmd) $COMPOSE_CMD stop 2>/dev/null || true echo "✅ 已停止" } # ============================================ # logs — 查看日志 # ============================================ do_logs() { load_env local COMPOSE_CMD COMPOSE_CMD=$(get_compose_cmd) $COMPOSE_CMD logs -f --tail=200 } # ============================================ # status — 容器状态 # ============================================ do_status() { load_env local COMPOSE_CMD COMPOSE_CMD=$(get_compose_cmd) $COMPOSE_CMD ps -a } # ============================================ # config — 检查配置 # ============================================ do_config() { load_env echo "=========================================" echo " 部署架构: ${DEPLOY_MODE:-1}($( [ "${DEPLOY_MODE:-1}" = "3" ] && echo "Spring Cloud" || echo "单体" ))" echo " Compose 文件: ${COMPOSE_FILE:-docker-compose.yml}" echo " MySQL: ${MYSQL_MODE:-docker}" echo " Redis: ${REDIS_MODE:-docker}" echo " MinIO: ${MINIO_MODE:-none}" echo " 网络: ${NETWORK_NAME:-ghb-net}" echo "=========================================" echo "" echo "=========================================" echo " .env 文件内容(敏感信息已脱敏)" echo "=========================================" grep -v '^#' "${DIR}/.env" | grep -v '^$' | sed 's/\(PASS=\).*/\1***/' | sed 's/\(PWD=\).*/\1***/' | sed 's/\(PASSWORD=\).*/\1***/' | sed 's/\(SECRET=\).*/\1***/' | sed 's/\(_KEY=\).*/\1***/' echo "" echo "=========================================" echo " Docker 解析后的环境变量" echo "=========================================" local COMPOSE_CMD COMPOSE_CMD=$(get_compose_cmd) $COMPOSE_CMD config 2>&1 | grep -E 'MYSQL_|REDIS_|JWT_|SPRING_|APP_|JAVA|MYSQL-|NACOS|GATEWAY|DB_|MINIO_|NETWORK_' | sed 's/\(PASS: \).*/\1***/' | sed 's/\(PWD: \).*/\1***/' | sed 's/\(SECRET: \).*/\1***/' | sed 's/\(_KEY: \).*/\1***/' echo "" if [ "${DEPLOY_MODE}" != "3" ]; then echo "=========================================" echo " application-prod.yml 内容" echo "=========================================" cat "${DIR}/config/application-prod.yml" echo "" fi if [ "${DEPLOY_MODE}" = "3" ]; then echo "=========================================" echo " Nacos 配置文件列表(部署后执行 init-nacos 导入)" echo "=========================================" ls -la "${DIR}/nacos/config/" 2>/dev/null || echo " (无)" echo "" fi } # ============================================ # init-nacos — 导入 Nacos 配置(仅 Cloud 模式) # ============================================ do_init_nacos() { load_env if [ "${DEPLOY_MODE}" != "3" ]; then echo "❌ 当前部署架构为 ${DEPLOY_MODE:-1},Nacos 配置仅适用于 Spring Cloud 模式" exit 1 fi if [ ! -f "${DIR}/init-nacos.sh" ]; then echo "❌ 未找到 init-nacos.sh" exit 1 fi NACOS_URL="http://localhost:${NACOS_PORT:-8848}" bash "${DIR}/init-nacos.sh" "$@" } # ============================================ # help # ============================================ do_help() { echo "用法: bash deploy.sh <命令> [分支]" echo "" echo "命令:" echo " init 交互式初始化 .env 配置文件" echo " start 停旧容器 → 启动(日常使用,不重新编译)" echo " build [分支] 编译镜像(代码变更后用一次)" echo " rebuild [分支] 拉取分支代码 + 编译 + 启动(一步到位)" echo " stop 停止所有容器" echo " logs 查看日志" echo " status 查看容器状态" echo " config 检查配置(不启动)" echo " init-db 初始化外接数据库表(仅 external MySQL)" echo " init-nacos 初始化 Nacos 配置(仅 Spring Cloud 模式)" echo "" echo "部署架构:" echo " 1. 单体 → docker-compose.yml" echo " 3. Spring Cloud → docker-compose.cloud.yml" echo "" echo "中间件策略(init 时独立选择):" echo " MySQL: docker(Docker 安装)/ external(连接外部)" echo " Redis: docker(Docker 安装)/ external(连接外部)" echo " MinIO: docker(Docker 安装)/ external(连接外部)/ none(不使用)" echo "" echo "示例:" echo " bash deploy.sh init # 首次:交互式配置" echo " bash deploy.sh build # 编译镜像" echo " bash deploy.sh start # 启动" echo " bash deploy.sh rebuild test # 拉取 test 分支 + 编译 + 启动" echo " bash deploy.sh logs # 查看日志" } # ============================================ # 路由 # ============================================ case "$CMD" in init) do_init ;; start) do_start ;; build) do_build ;; rebuild) do_rebuild ;; stop) do_stop ;; logs) do_logs ;; status) do_status ;; config) do_config ;; init-nacos) do_init_nacos ;; init-db) do_init_db ;; help|--help|-h) do_help ;; *) do_help ;; esac