#!/usr/bin/env bash # 在线订餐管理系统后台 Docker Compose 启动脚本(仅后台容器,MySQL/Redis 使用现网) # 默认镜像: harbor.sxidc.com/ywzx/fenyangwenlv:v1.0.0 # # 用法: # ./start.sh up # 本地编译 + 构建镜像 + 启动(输出精简) # ./start.sh up --pull # 从 Harbor 拉取镜像并启动 # ./start.sh up -v # 显示完整编译/构建日志 # ./start.sh pull # 仅拉取镜像 # ./start.sh push # 本地打包、构建并推送到 Harbor(需 docker login harbor.sxidc.com) # ./start.sh build # 本地构建镜像(不打 tag 推送) # ./start.sh build-jar # 仅 Maven 打包 # ./start.sh logs # 查看日志 # ./start.sh down # 停止容器 set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MAVEN_ROOT="" BUILD_CONTEXT="" DOCKER_DIR="" ENV_FILE="" COMPOSE_FILE="" DOCKERFILE_PATH="" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log_info() { echo -e "${GREEN}[INFO]${NC} $*"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } log_error() { echo -e "${RED}[ERROR]${NC} $*"; } require_cmd() { if ! command -v "$1" >/dev/null 2>&1; then log_error "未找到命令: $1" exit 1 fi } load_env() { # shellcheck disable=SC1090 set -a source "${ENV_FILE}" 2>/dev/null || true set +a } # 判断目录是否为 Maven 多模块根(含 web-starter 子模块) is_maven_project_root() { local dir="$1" [[ -f "${dir}/pom.xml" && -f "${dir}/web-starter/pom.xml" ]] } # 解析 docker 目录、compose 文件与 Maven 工程根(兼容脚本在 docker/ 或部署根目录两种布局) init_paths() { if [[ -f "${SCRIPT_DIR}/docker-compose.yml" ]]; then DOCKER_DIR="${SCRIPT_DIR}" elif [[ -f "${SCRIPT_DIR}/docker/docker-compose.yml" ]]; then DOCKER_DIR="${SCRIPT_DIR}/docker" else log_error "未找到 docker-compose.yml(请在 docker/ 目录或部署根目录执行 start.sh)" exit 1 fi ENV_FILE="${DOCKER_DIR}/.env" COMPOSE_FILE="${DOCKER_DIR}/docker-compose.yml" if [[ -f "${DOCKER_DIR}/Dockerfile" ]]; then DOCKERFILE_PATH="${DOCKER_DIR}/Dockerfile" else DOCKERFILE_PATH="${DOCKER_DIR}/docker/Dockerfile" fi # 先读 .env,允许 PROJECT_ROOT 显式指定 Maven 根 if [[ -f "${ENV_FILE}" ]]; then load_env fi local from_env="${PROJECT_ROOT:-}" MAVEN_ROOT="" if [[ -n "${from_env}" ]] && is_maven_project_root "${from_env}"; then MAVEN_ROOT="$(cd "${from_env}" && pwd)" else local candidate for candidate in \ "${SCRIPT_DIR}" \ "${SCRIPT_DIR}/.." \ "${DOCKER_DIR}" \ "${DOCKER_DIR}/.." \ "${DOCKER_DIR}/../TravelWebSystem" \ "${SCRIPT_DIR}/TravelWebSystem"; do if is_maven_project_root "${candidate}"; then MAVEN_ROOT="$(cd "${candidate}" && pwd)" break fi done fi BUILD_CONTEXT="${MAVEN_ROOT:-${DOCKER_DIR}}" export MAVEN_ROOT export BUILD_CONTEXT export PROJECT_ROOT="${BUILD_CONTEXT}" export DOCKERFILE_PATH } compose() { if docker compose version >/dev/null 2>&1; then docker compose --env-file "${ENV_FILE}" -f "${COMPOSE_FILE}" "$@" elif command -v docker-compose >/dev/null 2>&1; then docker-compose --env-file "${ENV_FILE}" -f "${COMPOSE_FILE}" "$@" else log_error "未找到 docker compose / docker-compose,请安装 Docker Compose v2" exit 1 fi } default_jar_path() { echo "${APP_JAR:-web-starter/target/web-starter-1.0-SNAPSHOT.jar}" } resolved_jar() { local jar jar="$(default_jar_path)" if [[ "${jar}" = /* ]]; then echo "${jar}" else echo "${BUILD_CONTEXT}/${jar}" fi } ensure_env() { if [[ ! -f "${ENV_FILE}" ]]; then if [[ -f "${SCRIPT_DIR}/.env.example" ]]; then cp "${SCRIPT_DIR}/.env.example" "${ENV_FILE}" log_warn "已生成 ${ENV_FILE},请填写现网 MYSQL_* / REDIS_* 后再启动" else log_error "缺少 ${ENV_FILE} 与 .env.example" exit 1 fi fi load_env local missing=0 for key in MYSQL_HOST MYSQL_USER MYSQL_PASSWORD REDIS_HOST; do if ! grep -q "^${key}=." "${ENV_FILE}" 2>/dev/null; then log_error ".env 缺少或未填写: ${key}" missing=1 fi local val val="$(grep "^${key}=" "${ENV_FILE}" | cut -d= -f2- | tr -d '\r')" if [[ -z "${val}" || "${val}" == "请填写现网密码" ]]; then log_error ".env 中 ${key} 仍为占位值,请改成现网配置" missing=1 fi done if [[ "${missing}" -eq 1 ]]; then exit 1 fi mkdir -p "${DOCKER_DIR}/config" } ensure_docker() { require_cmd docker if ! docker info >/dev/null 2>&1; then log_error "Docker 未运行,请先启动 Docker Desktop / dockerd" exit 1 fi } image_name() { load_env echo "${DOCKER_IMAGE:-harbor.sxidc.com/ywzx/fenyangwenlv:v1.0.0}" } BUILD_LOG="" COMPOSE_LOG="" is_verbose() { [[ "${VERBOSE:-0}" -eq 1 ]] } ensure_jar() { local jar jar="$(resolved_jar)" if [[ -f "${jar}" && "${SKIP_MAVEN_BUILD:-0}" == "1" ]]; then log_info "已存在 JAR,跳过 Maven 编译: ${jar}" return 0 fi if [[ -z "${MAVEN_ROOT}" ]]; then if [[ -f "${jar}" ]]; then log_warn "未找到 Maven 工程根目录,使用已有 JAR: ${jar}" return 0 fi log_error "未找到 Maven 工程(缺少 pom.xml 与 web-starter 模块)" log_error "请任选其一:" log_error " 1) 在 .env 设置 PROJECT_ROOT=完整源码目录(含 pom.xml、web-starter/)" log_error " 2) 放置 JAR 到 ${jar} 并设置 SKIP_MAVEN_BUILD=1" log_error " 3) 使用 Harbor 镜像: ./start.sh up --pull" exit 1 fi require_cmd mvn log_info "Maven 工程根: ${MAVEN_ROOT}" if is_verbose; then log_info "Maven 打包 web-starter(跳过测试)..." (cd "${MAVEN_ROOT}" && mvn -B -f pom.xml -pl web-starter -am package -DskipTests) else log_info "正在编译..." : > "${BUILD_LOG}" if ! (cd "${MAVEN_ROOT}" && mvn -B -q -f pom.xml -pl web-starter -am package -DskipTests >>"${BUILD_LOG}" 2>&1); then log_error "编译失败,日志: ${BUILD_LOG}" tail -n 50 "${BUILD_LOG}" >&2 exit 1 fi fi if [[ ! -f "${jar}" ]]; then log_error "未找到构建产物: ${jar}" exit 1 fi } compose_up_build() { if is_verbose; then log_info "构建镜像并启动容器..." compose up -d --build return fi log_info "正在构建镜像并启动..." : > "${COMPOSE_LOG}" if ! compose up -d --build --quiet-pull >>"${COMPOSE_LOG}" 2>&1; then log_error "构建或启动失败,日志: ${COMPOSE_LOG}" tail -n 50 "${COMPOSE_LOG}" >&2 exit 1 fi } compose_build() { if is_verbose; then compose build backend return fi : > "${COMPOSE_LOG}" if ! compose build backend >>"${COMPOSE_LOG}" 2>&1; then log_error "镜像构建失败,日志: ${COMPOSE_LOG}" tail -n 50 "${COMPOSE_LOG}" >&2 exit 1 fi } print_endpoints() { log_info "镜像: $(image_name)" log_info "后台 API: http://localhost:${SERVER_PORT:-8086}" log_info "Swagger: http://localhost:${SERVER_PORT:-8086}/swagger-ui.html" log_info "MySQL: ${MYSQL_HOST}:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-xinghc_travel}" log_info "Redis: ${REDIS_HOST}:${REDIS_PORT:-6379} db=${REDIS_DB:-1}" log_info "查看日志: ${SCRIPT_DIR}/start.sh logs" } cmd_pull() { ensure_docker ensure_env log_info "拉取镜像 $(image_name) ..." compose pull backend } cmd_up() { local do_pull=0 local do_no_build=0 VERBOSE=0 for arg in "$@"; do case "${arg}" in --pull) do_pull=1 ;; --no-build) do_no_build=1 ;; -v|--verbose) VERBOSE=1 ;; esac done ensure_docker ensure_env if [[ "${do_pull}" -eq 1 ]]; then log_info "拉取镜像并启动: $(image_name)" compose pull backend compose up -d elif [[ "${do_no_build}" -eq 1 ]]; then log_info "启动已有镜像: $(image_name)" compose up -d else log_info "构建并启动后台: $(image_name)" ensure_jar compose_up_build log_info "启动完成" fi compose ps print_endpoints } cmd_build() { ensure_docker ensure_env VERBOSE="${VERBOSE:-0}" if [[ ! -f "$(resolved_jar)" ]]; then ensure_jar fi log_info "构建镜像: $(image_name)" compose_build } cmd_push() { ensure_docker ensure_env VERBOSE="${VERBOSE:-0}" ensure_jar local img img="$(image_name)" log_info "构建并推送: ${img}" compose_build docker push "${img}" log_info "推送完成: ${img}" } cmd_logs() { ensure_docker ensure_env compose logs -f --tail=200 backend } cmd_down() { ensure_docker ensure_env if [[ "${1:-}" == "-v" ]]; then log_warn "将删除 backend 数据卷(上传文件、日志等)" compose down -v else compose down fi } cmd_ps() { ensure_docker ensure_env compose ps -a } cmd_restart() { ensure_docker ensure_env compose restart backend } usage() { cat < 命令: up [--pull] [--no-build] [-v] 构建并启动(--pull 用 Harbor;--no-build 不编译/不构建镜像) pull 仅拉取 Harbor 镜像 push 本地打包、构建并 push 到 Harbor(需 docker login) build 仅本地构建镜像 build-jar 仅 Maven 打包 logs 跟踪 backend 日志 ps 查看容器状态 restart 重启 backend down 停止容器(保留数据卷) down -v 停止并删除数据卷 环境变量(.env): DOCKER_IMAGE 默认 harbor.sxidc.com/ywzx/fenyangwenlv:v1.0.0 PROJECT_ROOT Maven 工程根(含 pom.xml、web-starter/);未设置则自动探测 SKIP_MAVEN_BUILD=1 跳过 mvn,使用已有 JAR SPRING_ARGS 传给 java -jar 的 Spring 参数(如 --spring.config.location=...) HOST_CONFIG_DIR 宿主机配置目录,挂载为 /serverdata/config(默认 ./config) 外部配置: 复制 config/application.yaml.example 为 config/application.yaml 后修改 EOF } main() { init_paths BUILD_LOG="${DOCKER_DIR}/.build.log" COMPOSE_LOG="${DOCKER_DIR}/.compose.log" local cmd="${1:-up}" shift || true case "${cmd}" in up) cmd_up "$@" ;; pull) cmd_pull ;; push) cmd_push ;; build) cmd_build ;; build-jar) VERBOSE=1; ensure_jar ;; logs) cmd_logs ;; down) cmd_down "$@" ;; ps) cmd_ps ;; restart) cmd_restart ;; -h|--help|help) usage ;; *) log_error "未知命令: ${cmd}" usage exit 1 ;; esac } main "$@"