#!/bin/bash ############################################## # 校园新闻管理系统 - 构建和打包脚本 # 功能: # 1. 从git拉取最新代码 # 2. 编译后端jar包 # 3. 构建前端dist # 4. 制作Docker镜像(支持基础镜像和应用镜像) # 5. 保存镜像到文件(用于离线部署) # # 使用方法: # ./build.sh [target] [options] [version] # # 目标(target): # base-serv - 构建后端基础镜像(包含Python依赖) # base-web - 构建前端基础镜像(Node) # base-all - 构建所有基础镜像 # serv - 构建后端服务镜像 # web - 构建前端服务镜像(Node + Vite Preview) # mysql - 构建MySQL镜像 # all - 构建所有应用镜像(默认) # # # 选项(options): # build - 编译代码(serv和web需要) # save - 保存镜像到tar文件(配合build自动保存构建版本) # save=YYYYMMDD_HHMMSS - 保存指定版本的镜像 # # 保存规则: # - build save → 自动保存刚构建的版本(推荐) # - save=20251124_xxx → 保存指定版本(必须已存在) # - 只使用save → 报错,必须指定版本 # # 示例: # ./build.sh base-all # 构建所有基础镜像 # ./build.sh serv build save # 编译+构建+保存(自动使用构建版本)✅ # ./build.sh all build save # 完整构建流程 # ./build.sh serv save=20251124_143025 # 保存已存在的指定版本 # ./build.sh serv save # ❌ 错误:必须指定版本 ############################################## set -e # 遇到错误立即退出 # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # 日志函数 log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_step() { echo -e "${BLUE}[STEP]${NC} $1" } # 项目路径 PROJECT_ROOT=$(cd "$(dirname "$0")" && pwd) SERV_PATH="${PROJECT_ROOT}/schoolNewsServ" WEB_PATH="${PROJECT_ROOT}/schoolNewsWeb" DOCKER_PATH="${PROJECT_ROOT}/docker" BUILD_OUTPUT="${PROJECT_ROOT}/build_output" IMAGE_VERSION=$(date +%Y%m%d_%H%M%S) # 解析命令行参数 BUILD_TARGET="${1:-all}" # 默认构建所有应用镜像 shift || true DO_BUILD=false DO_SAVE=false SAVE_VERSION="" # 要保存的版本号 # 解析选项 for arg in "$@"; do case $arg in build) DO_BUILD=true ;; save) DO_SAVE=true # 如果不指定版本,使用当前构建的版本 ;; save=*) DO_SAVE=true SAVE_VERSION="${arg#save=}" ;; *) log_warn "未知选项: $arg" ;; esac done # 确定保存版本 if [ "${DO_SAVE}" = true ]; then if [ -z "${SAVE_VERSION}" ]; then if [ "${DO_BUILD}" = true ]; then # 如果有build参数,保存刚构建的版本 SAVE_VERSION="${IMAGE_VERSION}" else # 如果没有build参数且未指定版本,提示错误 echo "" log_error "❌ 错误:只使用save时必须指定版本" echo "" echo "正确用法:" echo " 1. 保存指定版本: ./build.sh ${BUILD_TARGET} save=20251124_143025" echo " 2. 构建并保存: ./build.sh ${BUILD_TARGET} build save" echo "" echo "查看可用版本:" echo " docker images | grep school-news" echo "" exit 1 fi fi fi echo "==========================================" echo "校园新闻管理系统 - 构建脚本" echo "==========================================" log_info "构建目标: ${BUILD_TARGET}" log_info "构建版本: ${IMAGE_VERSION}" log_info "编译代码: ${DO_BUILD}" log_info "保存镜像: ${DO_SAVE}" if [ "${DO_SAVE}" = true ]; then if [ "${DO_BUILD}" = true ]; then log_info "保存版本: ${SAVE_VERSION} (自动使用构建版本)" else log_info "保存版本: ${SAVE_VERSION} (指定版本)" fi fi log_info "项目路径: ${PROJECT_ROOT}" echo "==========================================" echo "" # 创建输出目录 mkdir -p "${BUILD_OUTPUT}" # ================================================ # 构建函数 # ================================================ # 构建后端基础镜像 build_base_serv() { log_step "构建后端基础镜像(Base Serv)" cd "${PROJECT_ROOT}" log_info "执行: docker build -t school-news-base-serv:${IMAGE_VERSION} -f docker/Dockerfile.base-serv ." if docker build -t school-news-base-serv:${IMAGE_VERSION} -f docker/Dockerfile.base-serv .; then docker tag school-news-base-serv:${IMAGE_VERSION} school-news-base-serv:latest log_info "✅ 后端基础镜像构建成功" log_info " 镜像标签: school-news-base-serv:${IMAGE_VERSION}" log_info " 镜像标签: school-news-base-serv:latest" else log_error "❌ 后端基础镜像构建失败" exit 1 fi echo "" } # 构建MySQL镜像 build_mysql() { log_step "构建MySQL镜像" cd "${PROJECT_ROOT}" log_info "执行: docker build -t school-news-mysql:${IMAGE_VERSION} -f docker/Dockerfile.mysql ." if docker build -t school-news-mysql:${IMAGE_VERSION} -f docker/Dockerfile.mysql .; then docker tag school-news-mysql:${IMAGE_VERSION} school-news-mysql:latest log_info "✅ MySQL镜像构建成功" log_info " 镜像标签: school-news-mysql:${IMAGE_VERSION}" log_info " 镜像标签: school-news-mysql:latest" else log_error "❌ MySQL镜像构建失败" exit 1 fi echo "" } # 构建后端服务镜像 build_serv() { log_step "构建后端服务镜像(Serv)" cd "${PROJECT_ROOT}" # 检查基础镜像 if ! docker images | grep -q "school-news-base-serv.*latest"; then log_error "基础镜像不存在: school-news-base-serv:latest" log_error "请先构建基础镜像:./build.sh base-serv" exit 1 fi # 检查jar包 if [ ! -f "${SERV_PATH}/admin/target/admin-1.0.0.jar" ]; then log_error "jar包不存在: ${SERV_PATH}/admin/target/admin-1.0.0.jar" log_error "请先编译后端项目:./build.sh serv build" exit 1 fi log_info "执行: docker build -t school-news-serv:${IMAGE_VERSION} -f docker/Dockerfile.serv ." if docker build -t school-news-serv:${IMAGE_VERSION} -f docker/Dockerfile.serv .; then docker tag school-news-serv:${IMAGE_VERSION} school-news-serv:latest log_info "✅ 后端服务镜像构建成功" log_info " 镜像标签: school-news-serv:${IMAGE_VERSION}" log_info " 镜像标签: school-news-serv:latest" else log_error "❌ 后端服务镜像构建失败" exit 1 fi echo "" } # 构建前端服务镜像 build_web() { log_step "构建前端服务镜像(Web)" cd "${PROJECT_ROOT}" # 检查dist目录 if [ ! -d "${WEB_PATH}/dist" ]; then log_error "dist目录不存在: ${WEB_PATH}/dist" log_error "请先构建前端项目:./build.sh web build" exit 1 fi log_info "执行: docker build -t school-news-web:${IMAGE_VERSION} -f docker/Dockerfile.web ." if docker build -t school-news-web:${IMAGE_VERSION} -f docker/Dockerfile.web .; then docker tag school-news-web:${IMAGE_VERSION} school-news-web:latest log_info "✅ 前端服务镜像构建成功" log_info " 镜像标签: school-news-web:${IMAGE_VERSION}" log_info " 镜像标签: school-news-web:latest" else log_error "❌ 前端服务镜像构建失败" exit 1 fi echo "" } # 编译后端 compile_serv() { log_step "编译后端jar包" cd "${SERV_PATH}" log_info "清理旧的构建..." mvn clean -q log_info "开始编译后端项目..." if mvn package -DskipTests -pl admin -am; then JAR_FILE="${SERV_PATH}/admin/target/admin-1.0.0.jar" JAR_SIZE=$(du -h "${JAR_FILE}" | cut -f1) log_info "✅ 后端编译成功: ${JAR_SIZE}" else log_error "❌ 后端编译失败" exit 1 fi echo "" } # 构建前端 compile_web() { log_step "构建前端dist" cd "${WEB_PATH}" log_info "清理旧的构建..." rm -rf dist log_info "开始构建前端项目..." if npm run build; then FILE_COUNT=$(find dist -type f | wc -l) DIST_SIZE=$(du -sh dist | cut -f1) log_info "✅ 前端构建成功: ${FILE_COUNT}个文件, ${DIST_SIZE}" else log_error "❌ 前端构建失败" exit 1 fi echo "" } # 保存镜像 save_images() { log_step "保存Docker镜像" cd "${BUILD_OUTPUT}" case ${BUILD_TARGET} in base-serv) save_image "school-news-base-serv" ;; mysql) save_image "school-news-mysql" ;; serv) save_image "school-news-serv" ;; web) save_image "school-news-web" ;; all) save_image "school-news-mysql" save_image "school-news-serv" save_image "school-news-web" ;; esac } # 保存单个镜像(使用指定版本或当前构建版本) save_image() { local IMAGE_NAME=$1 local VERSION="${SAVE_VERSION}" local IMAGE_FILE="${IMAGE_NAME}_${VERSION}.tar" # 检查镜像是否存在 if ! docker images | grep -q "${IMAGE_NAME}.*${VERSION}"; then log_error "❌ 镜像不存在: ${IMAGE_NAME}:${VERSION}" log_error " 请先构建该版本的镜像,或使用 docker images 查看可用版本" exit 1 fi log_info "保存${IMAGE_NAME}:${VERSION}镜像..." if docker save -o "${IMAGE_FILE}" ${IMAGE_NAME}:${VERSION}; then IMAGE_SIZE=$(du -h "${IMAGE_FILE}" | cut -f1) log_info "✅ 镜像已保存: ${IMAGE_SIZE}" log_info " 版本: ${IMAGE_NAME}:${VERSION}" log_info " 文件: ${BUILD_OUTPUT}/${IMAGE_FILE}" else log_error "❌ 镜像保存失败: ${IMAGE_NAME}:${VERSION}" exit 1 fi echo "" } # ================================================ # 主流程控制(函数化) # ================================================ git_update_if_needed() { # Git更新(可选) if [ "${DO_BUILD}" != true ]; then return fi log_step "Git Pull" cd "${PROJECT_ROOT}" if [[ $(git status --porcelain) ]]; then log_warn "检测到未提交的更改" read -p "是否继续拉取代码?(y/n): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then log_warn "跳过git pull" else git pull origin main 2>/dev/null || git pull origin master 2>/dev/null || true fi else git pull origin main 2>/dev/null || git pull origin master 2>/dev/null || true fi echo "" } run_compile() { # 执行编译 if [ "${DO_BUILD}" != true ]; then return fi case ${BUILD_TARGET} in serv) compile_serv ;; web) compile_web ;; all) compile_serv compile_web ;; esac } run_build_images() { # 执行镜像构建 case ${BUILD_TARGET} in base-serv) build_base_serv ;; mysql) build_mysql ;; serv) build_serv ;; web) build_web ;; all) build_mysql build_serv build_web ;; *) log_error "未知的构建目标: ${BUILD_TARGET}" echo "" echo "可用的构建目标:" echo " base-serv - 构建后端基础镜像(包含Python依赖)" echo " mysql - 构建MySQL镜像" echo " serv - 构建后端服务镜像" echo " web - 构建前端服务镜像(基于node:20-alpine)" echo " all - 构建所有应用镜像(默认)" echo "" echo "注意:" echo " - Nginx使用官方镜像 nginx:alpine,无需构建" echo " - Web基于官方Node镜像 node:20-alpine,无需base-web" exit 1 ;; esac } show_images_info() { # 查看镜像信息 log_info "Docker镜像列表:" docker images | grep -E "school-news-" | head -15 echo "" } print_summary() { # 构建完成 echo "==========================================" log_info "✅ 构建完成!" echo "==========================================" log_info "构建目标: ${BUILD_TARGET}" log_info "构建版本: ${IMAGE_VERSION}" log_info "输出目录: ${BUILD_OUTPUT}" echo "==========================================" } main() { git_update_if_needed run_compile run_build_images show_images_info # 保存镜像(可选) if [ "${DO_SAVE}" = true ]; then save_images fi print_summary } main