diff --git a/.dockerignore b/.dockerignore index a277fc3..c02c856 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,6 +8,7 @@ data/ *.iml docs/ frontend/ +scripts/ README.md Dockerfile docker-compose.yml diff --git a/.env.example b/.env.example index 56b7840..082cc5a 100644 --- a/.env.example +++ b/.env.example @@ -23,8 +23,8 @@ LAB_JWT_SECRET=lab-reservation-docker-secret-key-change-me-2026 JAVA_OPTS=-Xms256m -Xmx512m # ---------- 端口映射(宿主机端口) ---------- -# 前端 Nginx 端口,若本机 80 被占用可改为 8088 -WEB_PORT=80 +# 前端 Nginx 端口(默认 8088:rootless Podman 无法绑定 80 等特权端口) +WEB_PORT=8088 # 后端直连端口(调试用,前端经 Nginx 代理访问后端) BACKEND_PORT=8080 diff --git a/.gitignore b/.gitignore index 817a986..29678e6 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,5 @@ data/ ### 本地环境变量(保留示例文件) ### .env !.env.example + +mariadb-data diff --git a/Dockerfile b/Dockerfile index f58641b..2400321 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ # ===================================================================== # ---------- 构建阶段 ---------- -FROM maven:3.9-eclipse-temurin-21 AS build +FROM docker.io/library/maven:3.9-eclipse-temurin-21 AS build WORKDIR /build # MAVEN_MIRROR=false 时跳过阿里云镜像配置,使用默认中央仓库 @@ -29,7 +29,7 @@ RUN mvn -B -q -Dmaven.test.skip=true package \ && cp target/*.jar /build/app.jar # ---------- 运行阶段 ---------- -FROM eclipse-temurin:21-jre +FROM docker.io/library/eclipse-temurin:21-jre WORKDIR /opt/app # 以非 root 用户运行 diff --git a/README.md b/README.md index e85b638..f5ff3da 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ pnpm build # 生产构建(vue-tsc 类型检查 + vite build) ```bash cp .env.example .env # 可选:按需修改端口/密码/JWT 密钥 docker compose up -d --build # 构建并启动 mariadb + backend + frontend -# 前端:http://localhost (Nginx 托管静态资源并反代 /api) +# 前端:http://localhost:8088 (Nginx 托管静态资源并反代 /api) # 后端:http://localhost:8080 docker compose ps # 查看健康状态 docker compose down # 停止容器,保留数据库数据卷 @@ -57,10 +57,53 @@ docker compose down -v # 停止并清空数据库(下次启动重建 - 编排:`mariadb:11.4`(数据持久化到命名卷)+ 后端多阶段构建镜像(JRE 21,非 root 运行)+ Nginx 前端镜像 - 三个服务均配置健康检查,后端与前端会等待上游就绪后再启动 +- 国内加速:后端构建自动使用**阿里云 Maven 镜像**(`docker/maven-settings.xml`), + 前端使用 **npmmirror**(淘宝 npm 镜像);如需走官方源: + `docker build --build-arg MAVEN_MIRROR=false .`、`docker build --build-arg NPM_REGISTRY=https://registry.npmjs.org ./frontend` - 可用环境变量(见 `.env.example`):`WEB_PORT`(默认 80)、`BACKEND_PORT`(默认 8080)、 `DB_NAME` / `DB_USER` / `DB_PASSWORD` / `DB_ROOT_PASSWORD`、`LAB_JWT_SECRET`、`JAVA_OPTS` -### 4. 演示账号 +### 4. Podman 部署 + +Podman 的 compose 实现与 Docker Compose 在「镜像名/容器名解析」上存在差异, +直接 `podman compose up -d` 可能出现以下报错: + +``` +Error: short-name "lab-reservation_backend" did not resolve to an alias ... +Error: "lab-backend" is not a valid container, cannot be used as a dependency ... +``` + +原因是 compose 未先构建本地镜像,Podman 便把 `lab-reservation_backend` 当作待拉取的短名镜像, +而系统未配置 `unqualified-search-registries`。**先显式构建镜像即可解决**: + +```bash +./scripts/podman-deploy.sh up # 一键:构建镜像 -> 启动 -> 健康检查 -> 打印访问地址 +./scripts/podman-deploy.sh down # 停止(保留数据库数据卷) +./scripts/podman-deploy.sh down-v # 停止并清空数据库 +./scripts/podman-deploy.sh logs # 查看日志 +``` + +手动等价步骤: + +```bash +podman build -t lab-backend:latest . +podman build -t lab-frontend:latest ./frontend +podman compose up -d # 或 podman-compose up -d +``` + +Podman 注意事项: + +- **端口**:前端默认映射到 `8088`(rootless Podman 无法绑定 80 等特权端口), + 如需 80 可 `WEB_PORT=80 ./scripts/podman-deploy.sh up`,或将 + `net.ipv4.ip_unprivileged_port_start=0` 写入 `/etc/sysctl.d/` 后 `sysctl --system` +- **短名镜像**:本项目所有基础镜像已写成全限定名(`docker.io/library/...`)。 + 若自行添加镜像,建议同样全限定,或在 + `~/.config/containers/registries.conf` 配置 `unqualified-search-registries = ["docker.io"]` +- **旧版 podman-compose**:若提示不支持 `depends_on.condition`,请升级到 1.0.6+, + 或先 `podman-compose up -d mariadb`,等数据库健康后再 `podman-compose up -d backend frontend` +- **SELinux(Fedora/RHEL)**:本项目只使用命名卷,无需额外 `:Z` 标记;如自定义 bind mount 请自行加 `:Z` + +### 5. 演示账号 | 账号 | 密码 | 角色 | 典型能力 | | --- | --- | --- | --- | @@ -140,6 +183,7 @@ mvn verify # 运行全部测试并执行 JaCoCo 覆盖率门禁 ├── Dockerfile # 后端多阶段构建镜像 ├── docker-compose.yml # mariadb + backend + frontend 一键编排 ├── .env.example # 部署环境变量示例(复制为 .env 使用) +├── scripts/podman-deploy.sh # Podman 一键部署脚本 └── docs/design.md # 需求分析与详细设计 ``` diff --git a/docker-compose.yml b/docker-compose.yml index b784ec2..ba62a5a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,13 +2,17 @@ # 实验室预约与资源管理平台 - 一键部署编排 # # 启动:docker compose up -d --build -# 访问:前端 http://localhost:${WEB_PORT:-80} +# 访问:前端 http://localhost:${WEB_PORT:-8088} # 后端 http://localhost:${BACKEND_PORT:-8080} # 停止:docker compose down (保留数据库数据卷) # docker compose down -v (同时删除数据库数据,首次初始化会重建演示数据) # # 可通过环境变量覆盖:DB_NAME / DB_USER / DB_PASSWORD / DB_ROOT_PASSWORD / -# LAB_JWT_SECRET / WEB_PORT / BACKEND_PORT +# LAB_JWT_SECRET / JAVA_OPTS / WEB_PORT / BACKEND_PORT +# +# Podman 部署请使用 scripts/podman-deploy.sh(会先构建镜像再启动),或手动执行: +# podman build -t lab-backend:latest . && podman build -t lab-frontend:latest ./frontend +# podman compose up -d # ===================================================================== name: lab-reservation @@ -84,7 +88,7 @@ services: backend: condition: service_healthy ports: - - "${WEB_PORT:-80}:80" + - "${WEB_PORT:-8088}:80" networks: - lab-net diff --git a/frontend/Dockerfile b/frontend/Dockerfile index bb3b8bb..229f2a4 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -7,7 +7,7 @@ # ===================================================================== # ---------- 构建阶段 ---------- -FROM node:24-alpine AS build +FROM docker.io/library/node:24-alpine AS build WORKDIR /app # 使用 npmmirror 加速 npm 全局安装与依赖拉取 @@ -24,7 +24,7 @@ COPY . . RUN pnpm build # ---------- 运行阶段 ---------- -FROM nginx:1.27-alpine +FROM docker.io/library/nginx:1.27-alpine COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/dist /usr/share/nginx/html diff --git a/scripts/podman-deploy.sh b/scripts/podman-deploy.sh new file mode 100755 index 0000000..8c81848 --- /dev/null +++ b/scripts/podman-deploy.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# ===================================================================== +# 使用 Podman 部署实验室预约平台 +# +# 用法: +# ./scripts/podman-deploy.sh up # 构建镜像并启动(默认) +# ./scripts/podman-deploy.sh down # 停止并删除容器(保留数据库数据卷) +# ./scripts/podman-deploy.sh down-v # 停止并删除容器与数据卷(重建演示数据) +# ./scripts/podman-deploy.sh status # 查看容器状态 +# ./scripts/podman-deploy.sh logs # 跟随查看日志 +# +# 说明:Podman 的 compose 实现(podman-compose)在容器名/镜像名解析上与 +# Docker Compose 存在差异,常见报错为: +# Error: short-name "lab-reservation_backend" did not resolve to an alias ... +# 因此本脚本先显式构建 lab-backend:latest / lab-frontend:latest, +# 再交给 compose 启动,确保 compose 直接使用本地镜像而不会尝试拉取。 +# ===================================================================== +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +cd "${PROJECT_DIR}" + +# ---------- 选择可用的 compose 命令 ---------- +if podman compose version >/dev/null 2>&1; then + COMPOSE=(podman compose) +elif command -v podman-compose >/dev/null 2>&1; then + COMPOSE=(podman-compose) +else + echo "错误:未找到 podman compose / podman-compose,请先安装其一" >&2 + exit 1 +fi + +# ---------- 读取 .env 中的端口配置(不 source,避免值中含空格被当作命令执行) ---------- +read_env() { + [ -f .env ] && grep -E "^$1=" .env 2>/dev/null | tail -n 1 | cut -d= -f2- | tr -d '"' || true +} +WEB_PORT="${WEB_PORT:-$(read_env WEB_PORT)}" +BACKEND_PORT="${BACKEND_PORT:-$(read_env BACKEND_PORT)}" +WEB_PORT="${WEB_PORT:-8088}" +BACKEND_PORT="${BACKEND_PORT:-8080}" + +# ---------- 构建镜像 ---------- +build_images() { + echo "==> 构建后端镜像 lab-backend:latest(首次构建需拉取 Maven 依赖)" + podman build -t lab-backend:latest . + echo "==> 构建前端镜像 lab-frontend:latest" + podman build -t lab-frontend:latest ./frontend +} + +# ---------- 等待 HTTP 服务就绪 ---------- +wait_ready() { + local name="$1" url="$2" tries=60 code + for _ in $(seq 1 "${tries}"); do + code="$(curl -s -o /dev/null -w '%{http_code}' "${url}" || true)" + if [ -n "${code}" ] && [ "${code}" != "000" ]; then + echo " ${name} 就绪:${url}(HTTP ${code})" + return 0 + fi + sleep 2 + done + echo " 警告:${name} 未在预期时间内就绪,请执行 '$0 logs' 查看日志" >&2 + return 1 +} + +ACTION="${1:-up}" +case "${ACTION}" in + up) + build_images + echo "==> 启动容器" + "${COMPOSE[@]}" up -d + echo "==> 等待服务就绪" + # 后端返回 401 也代表进程已起来 + wait_ready "后端" "http://localhost:${BACKEND_PORT}/api/labs" || true + wait_ready "前端" "http://localhost:${WEB_PORT}/" || true + echo + echo "部署完成:" + echo " 前端地址:http://localhost:${WEB_PORT}" + echo " 后端地址:http://localhost:${BACKEND_PORT}" + echo " 演示账号:admin/admin123、teacher1/123456、student1/123456" + echo + "${COMPOSE[@]}" ps || podman ps --filter "name=lab-" + ;; + build) + build_images + ;; + down) + "${COMPOSE[@]}" down + ;; + down-v) + "${COMPOSE[@]}" down -v + ;; + status) + "${COMPOSE[@]}" ps || podman ps --filter "name=lab-" + ;; + logs) + "${COMPOSE[@]}" logs -f --tail=100 + ;; + *) + echo "用法:$0 [up|build|down|down-v|status|logs]" >&2 + exit 1 + ;; +esac