start.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. #!/usr/bin/env bash
  2. # 在线订餐管理系统后台 Docker Compose 启动脚本(仅后台容器,MySQL/Redis 使用现网)
  3. # 默认镜像: harbor.sxidc.com/ywzx/fenyangwenlv:v1.0.0
  4. #
  5. # 用法:
  6. # ./start.sh up # 本地编译 + 构建镜像 + 启动(输出精简)
  7. # ./start.sh up --pull # 从 Harbor 拉取镜像并启动
  8. # ./start.sh up -v # 显示完整编译/构建日志
  9. # ./start.sh pull # 仅拉取镜像
  10. # ./start.sh push # 本地打包、构建并推送到 Harbor(需 docker login harbor.sxidc.com)
  11. # ./start.sh build # 本地构建镜像(不打 tag 推送)
  12. # ./start.sh build-jar # 仅 Maven 打包
  13. # ./start.sh logs # 查看日志
  14. # ./start.sh down # 停止容器
  15. set -euo pipefail
  16. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  17. MAVEN_ROOT=""
  18. BUILD_CONTEXT=""
  19. DOCKER_DIR=""
  20. ENV_FILE=""
  21. COMPOSE_FILE=""
  22. DOCKERFILE_PATH=""
  23. RED='\033[0;31m'
  24. GREEN='\033[0;32m'
  25. YELLOW='\033[1;33m'
  26. NC='\033[0m'
  27. log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
  28. log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
  29. log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
  30. require_cmd() {
  31. if ! command -v "$1" >/dev/null 2>&1; then
  32. log_error "未找到命令: $1"
  33. exit 1
  34. fi
  35. }
  36. load_env() {
  37. # shellcheck disable=SC1090
  38. set -a
  39. source "${ENV_FILE}" 2>/dev/null || true
  40. set +a
  41. }
  42. # 判断目录是否为 Maven 多模块根(含 web-starter 子模块)
  43. is_maven_project_root() {
  44. local dir="$1"
  45. [[ -f "${dir}/pom.xml" && -f "${dir}/web-starter/pom.xml" ]]
  46. }
  47. # 解析 docker 目录、compose 文件与 Maven 工程根(兼容脚本在 docker/ 或部署根目录两种布局)
  48. init_paths() {
  49. if [[ -f "${SCRIPT_DIR}/docker-compose.yml" ]]; then
  50. DOCKER_DIR="${SCRIPT_DIR}"
  51. elif [[ -f "${SCRIPT_DIR}/docker/docker-compose.yml" ]]; then
  52. DOCKER_DIR="${SCRIPT_DIR}/docker"
  53. else
  54. log_error "未找到 docker-compose.yml(请在 docker/ 目录或部署根目录执行 start.sh)"
  55. exit 1
  56. fi
  57. ENV_FILE="${DOCKER_DIR}/.env"
  58. COMPOSE_FILE="${DOCKER_DIR}/docker-compose.yml"
  59. if [[ -f "${DOCKER_DIR}/Dockerfile" ]]; then
  60. DOCKERFILE_PATH="${DOCKER_DIR}/Dockerfile"
  61. else
  62. DOCKERFILE_PATH="${DOCKER_DIR}/docker/Dockerfile"
  63. fi
  64. # 先读 .env,允许 PROJECT_ROOT 显式指定 Maven 根
  65. if [[ -f "${ENV_FILE}" ]]; then
  66. load_env
  67. fi
  68. local from_env="${PROJECT_ROOT:-}"
  69. MAVEN_ROOT=""
  70. if [[ -n "${from_env}" ]] && is_maven_project_root "${from_env}"; then
  71. MAVEN_ROOT="$(cd "${from_env}" && pwd)"
  72. else
  73. local candidate
  74. for candidate in \
  75. "${SCRIPT_DIR}" \
  76. "${SCRIPT_DIR}/.." \
  77. "${DOCKER_DIR}" \
  78. "${DOCKER_DIR}/.." \
  79. "${DOCKER_DIR}/../TravelWebSystem" \
  80. "${SCRIPT_DIR}/TravelWebSystem"; do
  81. if is_maven_project_root "${candidate}"; then
  82. MAVEN_ROOT="$(cd "${candidate}" && pwd)"
  83. break
  84. fi
  85. done
  86. fi
  87. BUILD_CONTEXT="${MAVEN_ROOT:-${DOCKER_DIR}}"
  88. export MAVEN_ROOT
  89. export BUILD_CONTEXT
  90. export PROJECT_ROOT="${BUILD_CONTEXT}"
  91. export DOCKERFILE_PATH
  92. }
  93. compose() {
  94. if docker compose version >/dev/null 2>&1; then
  95. docker compose --env-file "${ENV_FILE}" -f "${COMPOSE_FILE}" "$@"
  96. elif command -v docker-compose >/dev/null 2>&1; then
  97. docker-compose --env-file "${ENV_FILE}" -f "${COMPOSE_FILE}" "$@"
  98. else
  99. log_error "未找到 docker compose / docker-compose,请安装 Docker Compose v2"
  100. exit 1
  101. fi
  102. }
  103. default_jar_path() {
  104. echo "${APP_JAR:-web-starter/target/web-starter-1.0-SNAPSHOT.jar}"
  105. }
  106. resolved_jar() {
  107. local jar
  108. jar="$(default_jar_path)"
  109. if [[ "${jar}" = /* ]]; then
  110. echo "${jar}"
  111. else
  112. echo "${BUILD_CONTEXT}/${jar}"
  113. fi
  114. }
  115. ensure_env() {
  116. if [[ ! -f "${ENV_FILE}" ]]; then
  117. if [[ -f "${SCRIPT_DIR}/.env.example" ]]; then
  118. cp "${SCRIPT_DIR}/.env.example" "${ENV_FILE}"
  119. log_warn "已生成 ${ENV_FILE},请填写现网 MYSQL_* / REDIS_* 后再启动"
  120. else
  121. log_error "缺少 ${ENV_FILE} 与 .env.example"
  122. exit 1
  123. fi
  124. fi
  125. load_env
  126. local missing=0
  127. for key in MYSQL_HOST MYSQL_USER MYSQL_PASSWORD REDIS_HOST; do
  128. if ! grep -q "^${key}=." "${ENV_FILE}" 2>/dev/null; then
  129. log_error ".env 缺少或未填写: ${key}"
  130. missing=1
  131. fi
  132. local val
  133. val="$(grep "^${key}=" "${ENV_FILE}" | cut -d= -f2- | tr -d '\r')"
  134. if [[ -z "${val}" || "${val}" == "请填写现网密码" ]]; then
  135. log_error ".env 中 ${key} 仍为占位值,请改成现网配置"
  136. missing=1
  137. fi
  138. done
  139. if [[ "${missing}" -eq 1 ]]; then
  140. exit 1
  141. fi
  142. mkdir -p "${DOCKER_DIR}/config"
  143. }
  144. ensure_docker() {
  145. require_cmd docker
  146. if ! docker info >/dev/null 2>&1; then
  147. log_error "Docker 未运行,请先启动 Docker Desktop / dockerd"
  148. exit 1
  149. fi
  150. }
  151. image_name() {
  152. load_env
  153. echo "${DOCKER_IMAGE:-harbor.sxidc.com/ywzx/fenyangwenlv:v1.0.0}"
  154. }
  155. BUILD_LOG=""
  156. COMPOSE_LOG=""
  157. is_verbose() {
  158. [[ "${VERBOSE:-0}" -eq 1 ]]
  159. }
  160. ensure_jar() {
  161. local jar
  162. jar="$(resolved_jar)"
  163. if [[ -f "${jar}" && "${SKIP_MAVEN_BUILD:-0}" == "1" ]]; then
  164. log_info "已存在 JAR,跳过 Maven 编译: ${jar}"
  165. return 0
  166. fi
  167. if [[ -z "${MAVEN_ROOT}" ]]; then
  168. if [[ -f "${jar}" ]]; then
  169. log_warn "未找到 Maven 工程根目录,使用已有 JAR: ${jar}"
  170. return 0
  171. fi
  172. log_error "未找到 Maven 工程(缺少 pom.xml 与 web-starter 模块)"
  173. log_error "请任选其一:"
  174. log_error " 1) 在 .env 设置 PROJECT_ROOT=完整源码目录(含 pom.xml、web-starter/)"
  175. log_error " 2) 放置 JAR 到 ${jar} 并设置 SKIP_MAVEN_BUILD=1"
  176. log_error " 3) 使用 Harbor 镜像: ./start.sh up --pull"
  177. exit 1
  178. fi
  179. require_cmd mvn
  180. log_info "Maven 工程根: ${MAVEN_ROOT}"
  181. if is_verbose; then
  182. log_info "Maven 打包 web-starter(跳过测试)..."
  183. (cd "${MAVEN_ROOT}" && mvn -B -f pom.xml -pl web-starter -am package -DskipTests)
  184. else
  185. log_info "正在编译..."
  186. : > "${BUILD_LOG}"
  187. if ! (cd "${MAVEN_ROOT}" && mvn -B -q -f pom.xml -pl web-starter -am package -DskipTests >>"${BUILD_LOG}" 2>&1); then
  188. log_error "编译失败,日志: ${BUILD_LOG}"
  189. tail -n 50 "${BUILD_LOG}" >&2
  190. exit 1
  191. fi
  192. fi
  193. if [[ ! -f "${jar}" ]]; then
  194. log_error "未找到构建产物: ${jar}"
  195. exit 1
  196. fi
  197. }
  198. compose_up_build() {
  199. if is_verbose; then
  200. log_info "构建镜像并启动容器..."
  201. compose up -d --build
  202. return
  203. fi
  204. log_info "正在构建镜像并启动..."
  205. : > "${COMPOSE_LOG}"
  206. if ! compose up -d --build --quiet-pull >>"${COMPOSE_LOG}" 2>&1; then
  207. log_error "构建或启动失败,日志: ${COMPOSE_LOG}"
  208. tail -n 50 "${COMPOSE_LOG}" >&2
  209. exit 1
  210. fi
  211. }
  212. compose_build() {
  213. if is_verbose; then
  214. compose build backend
  215. return
  216. fi
  217. : > "${COMPOSE_LOG}"
  218. if ! compose build backend >>"${COMPOSE_LOG}" 2>&1; then
  219. log_error "镜像构建失败,日志: ${COMPOSE_LOG}"
  220. tail -n 50 "${COMPOSE_LOG}" >&2
  221. exit 1
  222. fi
  223. }
  224. print_endpoints() {
  225. log_info "镜像: $(image_name)"
  226. log_info "后台 API: http://localhost:${SERVER_PORT:-8086}"
  227. log_info "Swagger: http://localhost:${SERVER_PORT:-8086}/swagger-ui.html"
  228. log_info "MySQL: ${MYSQL_HOST}:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-xinghc_travel}"
  229. log_info "Redis: ${REDIS_HOST}:${REDIS_PORT:-6379} db=${REDIS_DB:-1}"
  230. log_info "查看日志: ${SCRIPT_DIR}/start.sh logs"
  231. }
  232. cmd_pull() {
  233. ensure_docker
  234. ensure_env
  235. log_info "拉取镜像 $(image_name) ..."
  236. compose pull backend
  237. }
  238. cmd_up() {
  239. local do_pull=0
  240. local do_no_build=0
  241. VERBOSE=0
  242. for arg in "$@"; do
  243. case "${arg}" in
  244. --pull) do_pull=1 ;;
  245. --no-build) do_no_build=1 ;;
  246. -v|--verbose) VERBOSE=1 ;;
  247. esac
  248. done
  249. ensure_docker
  250. ensure_env
  251. if [[ "${do_pull}" -eq 1 ]]; then
  252. log_info "拉取镜像并启动: $(image_name)"
  253. compose pull backend
  254. compose up -d
  255. elif [[ "${do_no_build}" -eq 1 ]]; then
  256. log_info "启动已有镜像: $(image_name)"
  257. compose up -d
  258. else
  259. log_info "构建并启动后台: $(image_name)"
  260. ensure_jar
  261. compose_up_build
  262. log_info "启动完成"
  263. fi
  264. compose ps
  265. print_endpoints
  266. }
  267. cmd_build() {
  268. ensure_docker
  269. ensure_env
  270. VERBOSE="${VERBOSE:-0}"
  271. if [[ ! -f "$(resolved_jar)" ]]; then
  272. ensure_jar
  273. fi
  274. log_info "构建镜像: $(image_name)"
  275. compose_build
  276. }
  277. cmd_push() {
  278. ensure_docker
  279. ensure_env
  280. VERBOSE="${VERBOSE:-0}"
  281. ensure_jar
  282. local img
  283. img="$(image_name)"
  284. log_info "构建并推送: ${img}"
  285. compose_build
  286. docker push "${img}"
  287. log_info "推送完成: ${img}"
  288. }
  289. cmd_logs() {
  290. ensure_docker
  291. ensure_env
  292. compose logs -f --tail=200 backend
  293. }
  294. cmd_down() {
  295. ensure_docker
  296. ensure_env
  297. if [[ "${1:-}" == "-v" ]]; then
  298. log_warn "将删除 backend 数据卷(上传文件、日志等)"
  299. compose down -v
  300. else
  301. compose down
  302. fi
  303. }
  304. cmd_ps() {
  305. ensure_docker
  306. ensure_env
  307. compose ps -a
  308. }
  309. cmd_restart() {
  310. ensure_docker
  311. ensure_env
  312. compose restart backend
  313. }
  314. usage() {
  315. cat <<EOF
  316. 用法: $(basename "$0") <command>
  317. 命令:
  318. up [--pull] [--no-build] [-v] 构建并启动(--pull 用 Harbor;--no-build 不编译/不构建镜像)
  319. pull 仅拉取 Harbor 镜像
  320. push 本地打包、构建并 push 到 Harbor(需 docker login)
  321. build 仅本地构建镜像
  322. build-jar 仅 Maven 打包
  323. logs 跟踪 backend 日志
  324. ps 查看容器状态
  325. restart 重启 backend
  326. down 停止容器(保留数据卷)
  327. down -v 停止并删除数据卷
  328. 环境变量(.env):
  329. DOCKER_IMAGE 默认 harbor.sxidc.com/ywzx/fenyangwenlv:v1.0.0
  330. PROJECT_ROOT Maven 工程根(含 pom.xml、web-starter/);未设置则自动探测
  331. SKIP_MAVEN_BUILD=1 跳过 mvn,使用已有 JAR
  332. SPRING_ARGS 传给 java -jar 的 Spring 参数(如 --spring.config.location=...)
  333. HOST_CONFIG_DIR 宿主机配置目录,挂载为 /serverdata/config(默认 ./config)
  334. 外部配置: 复制 config/application.yaml.example 为 config/application.yaml 后修改
  335. EOF
  336. }
  337. main() {
  338. init_paths
  339. BUILD_LOG="${DOCKER_DIR}/.build.log"
  340. COMPOSE_LOG="${DOCKER_DIR}/.compose.log"
  341. local cmd="${1:-up}"
  342. shift || true
  343. case "${cmd}" in
  344. up) cmd_up "$@" ;;
  345. pull) cmd_pull ;;
  346. push) cmd_push ;;
  347. build) cmd_build ;;
  348. build-jar) VERBOSE=1; ensure_jar ;;
  349. logs) cmd_logs ;;
  350. down) cmd_down "$@" ;;
  351. ps) cmd_ps ;;
  352. restart) cmd_restart ;;
  353. -h|--help|help) usage ;;
  354. *)
  355. log_error "未知命令: ${cmd}"
  356. usage
  357. exit 1
  358. ;;
  359. esac
  360. }
  361. main "$@"