Docker容器化实战:将你的SpringBoot应用一键打包部署,告别环境不一致的烦恼!#第一部分

本文提供完整的SpringBoot应用Docker容器化实战指南,从基础概念到高级部署,包含详细的操作步骤和代码示例,帮助您彻底解决环境不一致问题。

环境准备和Docker安装

系统要求检查

在开始Docker化之前,确保系统满足基本要求。

bash 复制代码
#!/bin/bash
# docker_environment_check.sh - Docker环境检查脚本

set -euo pipefail

# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'

echo -e "${BLUE}=== Docker环境检查开始 ===${NC}"

# 检查操作系统
echo -e "\n${GREEN}1. 操作系统检查${NC}"
if [ -f /etc/os-release ]; then
    source /etc/os-release
    echo -e "   操作系统: $NAME $VERSION"
    echo -e "   内核版本: $(uname -r)"
else
    echo -e "   ${YELLOW}警告: 无法确定操作系统类型${NC}"
fi

# 检查系统架构
echo -e "\n${GREEN}2. 系统架构检查${NC}"
ARCH=$(uname -m)
echo -e "   系统架构: $ARCH"

# 检查内存
echo -e "\n${GREEN}3. 内存检查${NC}"
MEM_TOTAL=$(free -h | awk 'NR==2{print $2}')
MEM_AVAILABLE=$(free -h | awk 'NR==2{print $7}')
echo -e "   总内存: $MEM_TOTAL"
echo -e "   可用内存: $MEM_AVAILABLE"

# 检查磁盘空间
echo -e "\n${GREEN}4. 磁盘空间检查${NC}"
DISK_SPACE=$(df -h / | awk 'NR==2{print $4}')
echo -e "   根分区可用空间: $DISK_SPACE"

# 检查Docker是否已安装
echo -e "\n${GREEN}5. Docker安装状态检查${NC}"
if command -v docker &> /dev/null; then
    DOCKER_VERSION=$(docker --version | cut -d' ' -f3 | tr -d ',')
    echo -e "   ✅ Docker已安装: $DOCKER_VERSION"
    
    # 检查Docker服务状态
    if systemctl is-active --quiet docker; then
        echo -e "   ✅ Docker服务运行中"
    else
        echo -e "   ${YELLOW}⚠️ Docker服务未运行${NC}"
    fi
else
    echo -e "   ${RED}❌ Docker未安装${NC}"
fi

# 检查Docker Compose
echo -e "\n${GREEN}6. Docker Compose检查${NC}"
if command -v docker-compose &> /dev/null; then
    DOCKER_COMPOSE_VERSION=$(docker-compose --version | cut -d' ' -f3 | tr -d ',')
    echo -e "   ✅ Docker Compose已安装: $DOCKER_COMPOSE_VERSION"
else
    echo -e "   ${YELLOW}⚠️ Docker Compose未安装${NC}"
fi

# 总结
echo -e "\n${BLUE}=== 环境检查完成 ===${NC}"
echo -e "请根据检查结果安装缺失的组件"

Docker安装脚本

根据不同操作系统安装Docker和Docker Compose。

bash 复制代码
#!/bin/bash
# install_docker.sh - Docker安装脚本

set -euo pipefail

# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'

log() {
    echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}

error_exit() {
    echo -e "${RED}错误: $1${NC}" >&2
    exit 1
}

# 检测操作系统
detect_os() {
    if [ -f /etc/os-release ]; then
        source /etc/os-release
        OS=$ID
        VERSION=$VERSION_ID
    else
        error_exit "无法检测操作系统"
    fi
}

# 安装Docker
install_docker() {
    log "开始安装Docker..."
    
    case $OS in
        ubuntu|debian)
            install_docker_debian
            ;;
        centos|rhel|fedora)
            install_docker_centos
            ;;
        *)
            error_exit "不支持的操作系统: $OS"
            ;;
    esac
}

# Debian/Ubuntu系统安装Docker
install_docker_debian() {
    log "在Debian/Ubuntu系统上安装Docker..."
    
    # 卸载旧版本
    sudo apt-get remove -y docker docker-engine docker.io containerd runc || true
    
    # 安装依赖
    sudo apt-get update
    sudo apt-get install -y \
        apt-transport-https \
        ca-certificates \
        curl \
        gnupg \
        lsb-release
    
    # 添加Docker官方GPG密钥
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
    
    # 添加Docker仓库
    echo \
      "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
      $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    
    # 安装Docker引擎
    sudo apt-get update
    sudo apt-get install -y docker-ce docker-ce-cli containerd.io
    
    log "Docker安装完成"
}

# CentOS/RHEL系统安装Docker
install_docker_centos() {
    log "在CentOS/RHEL系统上安装Docker..."
    
    # 卸载旧版本
    sudo yum remove -y docker \
        docker-client \
        docker-client-latest \
        docker-common \
        docker-latest \
        docker-latest-logrotate \
        docker-logrotate \
        docker-engine
    
    # 安装依赖
    sudo yum install -y yum-utils
    
    # 添加Docker仓库
    sudo yum-config-manager \
        --add-repo \
        https://download.docker.com/linux/centos/docker-ce.repo
    
    # 安装Docker引擎
    sudo yum install -y docker-ce docker-ce-cli containerd.io
    
    log "Docker安装完成"
}

# 安装Docker Compose
install_docker_compose() {
    log "开始安装Docker Compose..."
    
    # 获取最新版本
    COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
    
    # 下载并安装
    sudo curl -L "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    
    # 设置执行权限
    sudo chmod +x /usr/local/bin/docker-compose
    
    # 创建符号链接
    sudo ln -sf /usr/local/bin/docker-compose /usr/bin/docker-compose
    
    log "Docker Compose ${COMPOSE_VERSION} 安装完成"
}

# 配置Docker
configure_docker() {
    log "配置Docker..."
    
    # 启动Docker服务
    sudo systemctl enable docker
    sudo systemctl start docker
    
    # 将当前用户添加到docker组(避免每次使用sudo)
    sudo usermod -aG docker $USER
    
    # 配置Docker镜像加速器(中国用户)
    if [ -f /etc/docker/daemon.json ]; then
        sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak
    fi
    
    sudo tee /etc/docker/daemon.json > /dev/null << EOF
{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com",
    "https://registry.docker-cn.com"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  },
  "data-root": "/var/lib/docker"
}
EOF
    
    # 重启Docker服务
    sudo systemctl daemon-reload
    sudo systemctl restart docker
    
    log "Docker配置完成"
}

# 验证安装
verify_installation() {
    log "验证Docker安装..."
    
    if docker --version; then
        log "✅ Docker安装成功"
    else
        error_exit "Docker安装失败"
    fi
    
    if docker-compose --version; then
        log "✅ Docker Compose安装成功"
    else
        error_exit "Docker Compose安装失败"
    fi
    
    # 测试运行容器
    log "测试运行Hello World容器..."
    if docker run --rm hello-world; then
        log "✅ Docker运行测试成功"
    else
        error_exit "Docker运行测试失败"
    fi
}

# 显示使用说明
show_usage() {
    cat << EOF
使用说明:
  ./install_docker.sh [选项]

选项:
  --help             显示此帮助信息
  --skip-compose     跳过Docker Compose安装
  --skip-configure   跳过Docker配置

示例:
  ./install_docker.sh                    # 完整安装
  ./install_docker.sh --skip-compose     # 不安装Docker Compose
  ./install_docker.sh --skip-configure   # 不进行配置
EOF
}

# 主函数
main() {
    local skip_compose=false
    local skip_configure=false
    
    # 解析参数
    while [[ $# -gt 0 ]]; do
        case $1 in
            --skip-compose)
                skip_compose=true
                shift
                ;;
            --skip-configure)
                skip_configure=true
                shift
                ;;
            --help)
                show_usage
                exit 0
                ;;
            *)
                error_exit "未知参数: $1"
                ;;
        esac
    done
    
    log "开始Docker安装流程..."
    
    # 检测操作系统
    detect_os
    
    # 安装Docker
    install_docker
    
    # 安装Docker Compose(除非跳过)
    if [ "$skip_compose" = false ]; then
        install_docker_compose
    else
        log "跳过Docker Compose安装"
    fi
    
    # 配置Docker(除非跳过)
    if [ "$skip_configure" = false ]; then
        configure_docker
    else
        log "跳过Docker配置"
    fi
    
    # 验证安装
    verify_installation
    
    log "🎉 Docker安装全部完成!"
    log "请重新登录或执行 'newgrp docker' 以使组权限生效"
    log "然后可以运行 'docker ps' 测试权限"
}

# 执行主函数
main "$@"

SpringBoot应用Docker化实战

创建示例SpringBoot应用

首先创建一个完整的SpringBoot应用作为演示。

java 复制代码
// src/main/java/com/example/demo/DemoApplication.java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@RestController
class HealthController {
    
    @GetMapping("/health")
    public Map<String, Object> health() {
        Map<String, Object> healthInfo = new HashMap<>();
        healthInfo.put("status", "UP");
        healthInfo.put("timestamp", LocalDateTime.now().toString());
        healthInfo.put("service", "SpringBoot Docker Demo");
        return healthInfo;
    }
    
    @GetMapping("/info")
    public Map<String, Object> info() {
        Map<String, Object> info = new HashMap<>();
        info.put("java.version", System.getProperty("java.version"));
        info.put("java.vendor", System.getProperty("java.vendor"));
        info.put("os.name", System.getProperty("os.name"));
        info.put("os.arch", System.getProperty("os.arch"));
        info.put("user.name", System.getProperty("user.name"));
        info.put("availableProcessors", Runtime.getRuntime().availableProcessors());
        info.put("maxMemory", Runtime.getRuntime().maxMemory() / (1024 * 1024) + " MB");
        info.put("totalMemory", Runtime.getRuntime().totalMemory() / (1024 * 1024) + " MB");
        return info;
    }
}

SpringBoot应用配置文件

properties 复制代码
# src/main/resources/application.properties
# 应用配置
spring.application.name=springboot-docker-demo
server.port=8080
server.servlet.context-path=/

# 日志配置
logging.level.com.example.demo=INFO
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n

# 健康检查
management.endpoint.health.enabled=true
management.endpoints.web.exposure.include=health,info

Maven构建配置

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.example</groupId>
    <artifactId>springboot-docker-demo</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <name>SpringBoot Docker Demo</name>
    <description>SpringBoot应用Docker容器化演示项目</description>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/>
    </parent>
    
    <properties>
        <java.version>11</java.version>
        <docker.image.prefix>springboot-demo</docker.image.prefix>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
            
            <!-- Docker镜像构建插件 -->
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <version>1.4.13</version>
                <configuration>
                    <repository>${docker.image.prefix}/${project.artifactId}</repository>
                    <tag>${project.version}</tag>
                    <buildArgs>
                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Docker化配置和脚本

基础Dockerfile配置

dockerfile 复制代码
# Dockerfile - SpringBoot应用容器化配置
# 多阶段构建,减小镜像体积

# 第一阶段:构建阶段
FROM maven:3.8.6-openjdk-11 AS builder

# 设置工作目录
WORKDIR /app

# 复制pom文件
COPY pom.xml .

# 下载依赖(利用Docker缓存层)
RUN mvn dependency:go-offline -B

# 复制源代码
COPY src ./src

# 构建应用
RUN mvn clean package -DskipTests

# 第二阶段:运行阶段
FROM openjdk:11-jre-slim

# 设置元数据
LABEL maintainer="your-email@example.com"
LABEL version="1.0.0"
LABEL description="SpringBoot Docker Demo Application"

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 创建应用用户(安全最佳实践)
RUN groupadd -r springboot && useradd -r -g springboot springboot

# 创建应用目录
RUN mkdir -p /app/logs && chown -R springboot:springboot /app

# 切换到应用用户
USER springboot

# 设置工作目录
WORKDIR /app

# 从构建阶段复制jar文件
COPY --from=builder --chown=springboot:springboot /app/target/*.jar app.jar

# 创建健康检查脚本
COPY --chown=springboot:springboot health-check.sh .

# 设置JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
ENV SPRING_PROFILES_ACTIVE="docker"

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
    CMD ./health-check.sh

# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar app.jar"]

健康检查脚本

bash 复制代码
#!/bin/bash
# health-check.sh - 应用健康检查脚本

set -e

# 健康检查端点
HEALTH_URL="http://localhost:8080/health"

# 超时时间(秒)
TIMEOUT=5

# 执行健康检查
response=$(curl -s -o /dev/null -w "%{http_code}" --max-time $TIMEOUT $HEALTH_URL || echo "000")

# 检查响应状态码
if [ "$response" = "200" ]; then
    echo "Health check: OK"
    exit 0
else
    echo "Health check: FAILED (HTTP $response)"
    exit 1
fi

优化版Dockerfile(针对生产环境)

dockerfile 复制代码
# Dockerfile.optimized - 生产环境优化版本
FROM eclipse-temurin:11-jre-focal as base

# 安装必要的工具
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    tzdata \
    && rm -rf /var/lib/apt/lists/*

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 创建非root用户
RUN groupadd --gid 1000 springboot && \
    useradd --uid 1000 --gid springboot --shell /bin/bash --create-home springboot

# 第二阶段:运行环境
FROM base as runtime

# 设置JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m \
    -XX:+UseG1GC \
    -XX:MaxGCPauseMillis=200 \
    -XX:+UnlockExperimentalVMOptions \
    -XX:+UseContainerSupport \
    -XX:InitialRAMPercentage=50.0 \
    -XX:MaxRAMPercentage=80.0 \
    -Djava.security.egd=file:/dev/./urandom"

# 设置应用参数
ENV SPRING_PROFILES_ACTIVE="docker,prod"
ENV SERVER_PORT=8080

# 创建应用目录
WORKDIR /app

# 复制jar文件
COPY --chown=springboot:springboot target/*.jar app.jar

# 复制启动脚本
COPY --chown=springboot:springboot docker-entrypoint.sh .

# 复制健康检查脚本
COPY --chown=springboot:springboot health-check.sh .

# 设置权限
RUN chmod +x docker-entrypoint.sh health-check.sh

# 创建日志目录
RUN mkdir -p logs && chown springboot:springboot logs

# 切换到非root用户
USER springboot

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD ./health-check.sh

# 使用入口点脚本
ENTRYPOINT ["./docker-entrypoint.sh"]

高级启动脚本

bash 复制代码
#!/bin/bash
# docker-entrypoint.sh - 高级启动脚本

set -e

echo "🚀 启动 SpringBoot 应用..."

# 设置默认JVM参数
if [ -z "$JAVA_OPTS" ]; then
    JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC"
fi

# 应用特定配置
if [ "$SPRING_PROFILES_ACTIVE" = "prod" ]; then
    echo "🔧 生产环境配置激活"
    JAVA_OPTS="$JAVA_OPTS -XX:+AlwaysPreTouch -XX:+ExitOnOutOfMemoryError"
fi

# 日志配置
if [ -n "$LOG_LEVEL" ]; then
    JAVA_OPTS="$JAVA_OPTS -Dlogging.level.com.example.demo=$LOG_LEVEL"
fi

# 显示启动信息
echo "📋 启动配置:"
echo "   工作目录: $(pwd)"
echo "   Java版本: $(java -version 2>&1 | head -1)"
echo "   JVM参数: $JAVA_OPTS"
echo "   Spring Profiles: $SPRING_PROFILES_ACTIVE"
echo "   应用端口: $SERVER_PORT"

# 等待依赖服务(如果有)
if [ -n "$WAIT_FOR_HOSTS" ]; then
    echo "⏳ 等待依赖服务..."
    IFS=',' read -ra HOSTS <<< "$WAIT_FOR_HOSTS"
    for host in "${HOSTS[@]}"; do
        IFS=':' read -ra ADDR <<< "$host"
        hostname=${ADDR[0]}
        port=${ADDR[1]:=80}
        
        echo "   等待 $hostname:$port..."
        while ! nc -z $hostname $port; do
            sleep 1
        done
        echo "   ✅ $hostname:$port 就绪"
    done
fi

# 启动应用
echo "🎯 启动应用..."
exec java $JAVA_OPTS -jar app.jar "$@"

Docker构建和部署脚本

自动化构建脚本

bash 复制代码
#!/bin/bash
# build_and_deploy.sh - 自动化构建和部署脚本

set -euo pipefail

# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'

# 配置变量
APP_NAME="springboot-docker-demo"
VERSION="1.0.0"
REGISTRY="localhost:5000"  # 本地 registry,生产环境替换为实际 registry
DOCKERFILE="Dockerfile"

# 日志函数
log() {
    echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}

error_exit() {
    echo -e "${RED}错误: $1${NC}" >&2
    exit 1
}

# 显示帮助信息
show_help() {
    cat << EOF
使用说明: $0 [选项]

选项:
  -h, --help          显示此帮助信息
  -b, --build         构建Docker镜像
  -t, --test          运行测试
  -p, --push          推送镜像到仓库
  -d, --deploy        部署到环境
  -e, --environment   指定环境 (dev/staging/prod)
  --version          指定版本号
  --no-cache         构建时不使用缓存

示例:
  $0 -b -t -p -d -e prod        # 完整CI/CD流程
  $0 -b --no-cache              # 不使用缓存构建
  $0 --version 2.0.0 -b -p      # 构建并推送特定版本
EOF
}

# 参数解析
parse_arguments() {
    local build=false
    local test=false
    local push=false
    local deploy=false
    local environment="dev"
    local no_cache=false
    
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help)
                show_help
                exit 0
                ;;
            -b|--build)
                build=true
                shift
                ;;
            -t|--test)
                test=true
                shift
                ;;
            -p|--push)
                push=true
                shift
                ;;
            -d|--deploy)
                deploy=true
                shift
                ;;
            -e|--environment)
                environment="$2"
                shift 2
                ;;
            --version)
                VERSION="$2"
                shift 2
                ;;
            --no-cache)
                no_cache=true
                shift
                ;;
            *)
                error_exit "未知参数: $1"
                ;;
        esac
    done
    
    # 如果没有指定任何操作,显示帮助
    if [ "$build" = false ] && [ "$test" = false ] && [ "$push" = false ] && [ "$deploy" = false ]; then
        show_help
        exit 0
    fi
    
    # 设置变量供后续使用
    BUILD=$build
    TEST=$test
    PUSH=$push
    DEPLOY=$deploy
    ENVIRONMENT=$environment
    NO_CACHE=$no_cache
}

# 前置检查
pre_checks() {
    log "执行前置检查..."
    
    # 检查Docker是否可用
    if ! command -v docker &> /dev/null; then
        error_exit "Docker未安装"
    fi
    
    # 检查Docker服务是否运行
    if ! docker info &> /dev/null; then
        error_exit "Docker服务未运行"
    fi
    
    # 检查必要的文件
    if [ ! -f "Dockerfile" ]; then
        error_exit "Dockerfile不存在"
    fi
    
    if [ ! -f "pom.xml" ]; then
        error_exit "pom.xml不存在"
    fi
    
    log "✅ 前置检查通过"
}

# 构建应用
build_application() {
    log "构建SpringBoot应用..."
    
    if [ ! -d "target" ] || [ "$NO_CACHE" = true ]; then
        # 清理并重新构建
        mvn clean package -DskipTests
    else
        # 仅打包
        mvn package -DskipTests
    fi
    
    if [ $? -eq 0 ]; then
        log "✅ 应用构建成功"
    else
        error_exit "应用构建失败"
    fi
}

# 构建Docker镜像
build_docker_image() {
    log "构建Docker镜像..."
    
    local cache_flag=""
    if [ "$NO_CACHE" = true ]; then
        cache_flag="--no-cache"
        log "不使用缓存构建"
    fi
    
    local image_tag="$APP_NAME:$VERSION"
    local full_tag="$REGISTRY/$APP_NAME:$VERSION"
    
    # 构建镜像
    docker build $cache_flag -t "$image_tag" -t "$full_tag" -f "$DOCKERFILE" .
    
    if [ $? -eq 0 ]; then
        log "✅ Docker镜像构建成功: $image_tag"
        log "✅ Docker镜像构建成功: $full_tag"
    else
        error_exit "Docker镜像构建失败"
    fi
}

# 运行测试
run_tests() {
    log "运行测试..."
    
    # 运行单元测试
    mvn test
    
    if [ $? -eq 0 ]; then
        log "✅ 单元测试通过"
    else
        error_exit "单元测试失败"
    fi
    
    # 运行容器测试
    log "运行容器测试..."
    
    local test_container_name="${APP_NAME}-test-$(date +%s)"
    local image_tag="$APP_NAME:$VERSION"
    
    # 启动测试容器
    docker run -d --name "$test_container_name" -p 8081:8080 "$image_tag"
    
    # 等待应用启动
    log "等待应用启动..."
    sleep 30
    
    # 测试健康端点
    local health_response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/health || echo "000")
    
    if [ "$health_response" = "200" ]; then
        log "✅ 健康检查通过"
    else
        docker logs "$test_container_name"
        error_exit "健康检查失败 (HTTP $health_response)"
    fi
    
    # 测试信息端点
    local info_response=$(curl -s http://localhost:8081/info | grep -q "java.version" && echo "OK" || echo "FAIL")
    
    if [ "$info_response" = "OK" ]; then
        log "✅ 信息端点测试通过"
    else
        error_exit "信息端点测试失败"
    fi
    
    # 清理测试容器
    docker stop "$test_container_name"
    docker rm "$test_container_name"
    
    log "✅ 所有测试通过"
}

# 推送镜像
push_image() {
    log "推送镜像到仓库..."
    
    local full_tag="$REGISTRY/$APP_NAME:$VERSION"
    
    # 检查仓库是否可用
    if ! docker push "$full_tag" &> /dev/null; then
        log "⚠️  镜像仓库不可用,尝试启动本地registry"
        
        # 启动本地registry(用于演示)
        docker run -d -p 5000:5000 --name registry registry:2 || true
        sleep 2
    fi
    
    # 推送镜像
    if docker push "$full_tag"; then
        log "✅ 镜像推送成功: $full_tag"
    else
        error_exit "镜像推送失败"
    fi
}

# 部署应用
deploy_application() {
    log "部署应用到 $ENVIRONMENT 环境..."
    
    case $ENVIRONMENT in
        dev)
            deploy_dev
            ;;
        staging)
            deploy_staging
            ;;
        prod)
            deploy_prod
            ;;
        *)
            error_exit "未知环境: $ENVIRONMENT"
            ;;
    esac
}

# 开发环境部署
deploy_dev() {
    log "部署到开发环境..."
    
    local container_name="$APP_NAME-dev"
    local image_tag="$APP_NAME:$VERSION"
    
    # 停止并删除现有容器
    docker stop "$container_name" 2>/dev/null || true
    docker rm "$container_name" 2>/dev/null || true
    
    # 启动新容器
    docker run -d \
        --name "$container_name" \
        -p 8080:8080 \
        -e SPRING_PROFILES_ACTIVE="dev,docker" \
        -e JAVA_OPTS="-Xms256m -Xmx512m" \
        --restart unless-stopped \
        "$image_tag"
    
    log "✅ 开发环境部署完成"
    log "   应用地址: http://localhost:8080"
    log "   健康检查: http://localhost:8080/health"
}

# 生产环境部署
deploy_prod() {
    log "部署到生产环境..."
    
    # 使用Docker Compose部署
    if [ -f "docker-compose.prod.yml" ]; then
        docker-compose -f docker-compose.prod.yml up -d
        log "✅ 生产环境部署完成"
    else
        error_exit "生产环境配置文件不存在"
    fi
}

# 主函数
main() {
    log "🚀 开始SpringBoot应用Docker化流程"
    
    # 解析参数
    parse_arguments "$@"
    
    # 前置检查
    pre_checks
    
    # 构建应用
    if [ "$BUILD" = true ]; then
        build_application
        build_docker_image
    fi
    
    # 运行测试
    if [ "$TEST" = true ]; then
        run_tests
    fi
    
    # 推送镜像
    if [ "$PUSH" = true ]; then
        push_image
    fi
    
    # 部署应用
    if [ "$DEPLOY" = true ]; then
        deploy_application
    fi
    
    log "🎉 所有操作完成!"
    
    # 显示部署信息
    if [ "$DEPLOY" = true ]; then
        echo ""
        echo "📋 部署摘要:"
        echo "   应用名称: $APP_NAME"
        echo "   版本: $VERSION"
        echo "   环境: $ENVIRONMENT"
        echo "   镜像: $REGISTRY/$APP_NAME:$VERSION"
        echo "   健康检查: http://localhost:8080/health"
    fi
}

# 执行主函数
main "$@"

Docker Compose编排配置

开发环境配置

yaml 复制代码
# docker-compose.dev.yml
version: '3.8'

services:
  springboot-app:
    build:
      context: .
      dockerfile: Dockerfile
    image: springboot-docker-demo:dev
    container_name: springboot-app-dev
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=dev,docker
      - JAVA_OPTS=-Xms256m -Xmx512m -XX:+UseG1GC
      - LOG_LEVEL=DEBUG
    volumes:
      - ./logs:/app/logs
      - ./config:/app/config
    networks:
      - springboot-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "./health-check.sh"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # MySQL数据库
  mysql-db:
    image: mysql:8.0
    container_name: mysql-dev
    environment:
      - MYSQL_ROOT_PASSWORD=rootpassword
      - MYSQL_DATABASE=springboot_demo
      - MYSQL_USER=appuser
      - MYSQL_PASSWORD=apppassword
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - springboot-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 10s
      retries: 5

  # Redis缓存
  redis:
    image: redis:7-alpine
    container_name: redis-dev
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - springboot-network
    restart: unless-stopped
    command: redis-server --appendonly yes

  # 监控工具
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus-dev
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    networks:
      - springboot-network
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana-dev
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - springboot-network
    restart: unless-stopped

networks:
  springboot-network:
    driver: bridge

volumes:
  mysql_data:
  redis_data:
  prometheus_data:
  grafana_data:

生产环境配置

yaml 复制代码
# docker-compose.prod.yml
version: '3.8'

services:
  springboot-app:
    image: ${REGISTRY:-localhost:5000}/springboot-docker-demo:${VERSION:-latest}
    container_name: springboot-app-prod
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod,docker
      - JAVA_OPTS=-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200
      - LOG_LEVEL=INFO
      - MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics
    volumes:
      - app_logs:/app/logs
    networks:
      - springboot-prod-network
    restart: always
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: '1.0'
        reservations:
          memory: 512M
          cpus: '0.5'
    healthcheck:
      test: ["CMD", "./health-check.sh"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  # Nginx负载均衡
  nginx:
    image: nginx:1.21-alpine
    container_name: nginx-prod
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    networks:
      - springboot-prod-network
    restart: always
    depends_on:
      - springboot-app

  # 数据库(生产环境建议使用外部数据库)
  # mysql:
  #   image: mysql:8.0
  #   container_name: mysql-prod
  #   environment:
  #     - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
  #     - MYSQL_DATABASE=${DB_NAME}
  #     - MYSQL_USER=${DB_USER}
  #     - MYSQL_PASSWORD=${DB_PASSWORD}
  #   volumes:
  #     - mysql_prod_data:/var/lib/mysql
  #   networks:
  #     - springboot-prod-network
  #   restart: always
  #   deploy:
  #     resources:
  #       limits:
  #         memory: 2G
  #         cpus: '2.0'

networks:
  springboot-prod-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

volumes:
  app_logs:
  mysql_prod_data:

Nginx配置

nginx 复制代码
# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream springboot_app {
        server springboot-app:8080;
    }

    # 日志格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';

    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log warn;

    # 基础配置
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    # 服务器配置
    server {
        listen 80;
        server_name localhost;

        # 安全头
        add_header X-Frame-Options DENY always;
        add_header X-Content-Type-Options nosniff always;
        add_header X-XSS-Protection "1; mode=block" always;

        # 静态资源缓存
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
            proxy_pass http://springboot_app;
        }

        # API路由
        location /api/ {
            proxy_pass http://springboot_app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            
            # 超时设置
            proxy_connect_timeout 30s;
            proxy_send_timeout 30s;
            proxy_read_timeout 30s;
        }

        # 健康检查
        location /health {
            proxy_pass http://springboot_app/health;
            proxy_set_header Host $host;
            access_log off;
        }

        # 根路径
        location / {
            proxy_pass http://springboot_app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

监控和日志管理

应用监控配置

yaml 复制代码
# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

scrape_configs:
  - job_name: 'springboot-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['springboot-app:8080']
    scrape_interval: 10s
    scrape_timeout: 5s

  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093

日志管理脚本

bash 复制代码
#!/bin/bash
# log_manager.sh - 容器日志管理脚本

set -euo pipefail

# 配置
LOG_DIR="./logs"
BACKUP_DIR="./logs/backup"
RETENTION_DAYS=7
CONTAINER_NAME="springboot-app"

# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'

log() {
    echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}

# 创建目录
create_directories() {
    mkdir -p "$LOG_DIR"
    mkdir -p "$BACKUP_DIR"
}

# 查看实时日志
view_logs() {
    log "查看容器日志: $CONTAINER_NAME"
    
    if docker ps --format "table {{.Names}}" | grep -q "$CONTAINER_NAME"; then
        docker logs -f "$CONTAINER_NAME"
    else
        echo -e "${RED}容器 $CONTAINER_NAME 未运行${NC}"
    fi
}

# 导出日志
export_logs() {
    local export_file="$BACKUP_DIR/logs_$(date +%Y%m%d_%H%M%S).tar.gz"
    
    log "导出容器日志到: $export_file"
    
    if docker ps --format "table {{.Names}}" | grep -q "$CONTAINER_NAME"; then
        docker logs "$CONTAINER_NAME" > "$LOG_DIR/container.log" 2>&1
        tar -czf "$export_file" -C "$LOG_DIR" .
        log "✅ 日志导出完成: $export_file"
    else
        echo -e "${RED}容器 $CONTAINER_NAME 未运行${NC}"
    fi
}

# 清理旧日志
cleanup_old_logs() {
    log "清理 $RETENTION_DAYS 天前的日志备份..."
    
    find "$BACKUP_DIR" -name "*.tar.gz" -type f -mtime +$RETENTION_DAYS -delete
    
    log "✅ 旧日志清理完成"
}

# 日志分析
analyze_logs() {
    local log_file="$LOG_DIR/container.log"
    
    if [ ! -f "$log_file" ]; then
        echo -e "${RED}日志文件不存在: $log_file${NC}"
        return 1
    fi
    
    log "分析应用日志..."
    
    echo -e "\n${YELLOW}=== 错误统计 ===${NC}"
    grep -i "error" "$log_file" | sort | uniq -c | sort -nr | head -10
    
    echo -e "\n${YELLOW}=== 警告统计 ===${NC}"
    grep -i "warn" "$log_file" | sort | uniq -c | sort -nr | head -10
    
    echo -e "\n${YELLOW}=== 请求统计 ===${NC}"
    grep "HTTP" "$log_file" | awk '{print $NF}' | sort | uniq -c | sort -nr | head -10
    
    echo -e "\n${YELLOW}=== 响应时间分析 ===${NC}"
    grep "Completed" "$log_file" | awk '{print $NF}' | \
    awk '{
        if ($1 < 100) a++; 
        else if ($1 < 500) b++; 
        else if ($1 < 1000) c++; 
        else d++;
    } 
    END {
        print " < 100ms: " a;
        print "100-500ms: " b;
        print "500-1000ms: " c;
        print " > 1000ms: " d;
    }'
}

# 显示帮助
show_help() {
    cat << EOF
使用说明: $0 [命令]

命令:
  view     查看实时日志
  export   导出日志到文件
  cleanup  清理旧日志
  analyze  分析日志
  help     显示此帮助信息

示例:
  $0 view      # 查看实时日志
  $0 export    # 导出日志
  $0 analyze   # 分析日志
EOF
}

# 主函数
main() {
    local command=${1:-help}
    
    create_directories
    
    case $command in
        view)
            view_logs
            ;;
        export)
            export_logs
            ;;
        cleanup)
            cleanup_old_logs
            ;;
        analyze)
            analyze_logs
            ;;
        help|*)
            show_help
            ;;
    esac
}

# 执行主函数
main "$@"

Docker化流程架构图

以下图表展示了完整的SpringBoot应用Docker化流程:

flowchart TD A[SpringBoot应用代码] --> B[Maven构建] B --> C[Docker镜像构建] C --> D[镜像测试验证] D --> E[镜像推送仓库] E --> F[容器化部署] F --> G[监控日志] G --> H[性能分析] B --> B1[pom.xml配置] B --> B2[应用打包] C --> C1[Dockerfile配置] C --> C2[多阶段构建] C --> C3[镜像优化] D --> D1[健康检查] D --> D2[功能测试] D --> D3[性能测试] E --> E1[Docker Registry] E --> E2[版本标签] F --> F1[Docker Compose] F --> F2[容器编排] F --> F3[服务发现] G --> G1[Prometheus监控] G --> G2[Grafana仪表板] G --> G3[日志聚合] H --> H1[性能调优] H --> H2[资源优化] H --> H3[自动扩缩容] style A fill:#3498db,color:#fff style F fill:#27ae60,color:#fff style H fill:#e74c3c,color:#fff style C fill:#9b59b6,color:#fff

高级特性和最佳实践

安全加固配置

dockerfile 复制代码
# Dockerfile.security - 安全加固版本
FROM eclipse-temurin:11-jre-focal as base

# 安全扫描和更新
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# 创建非特权用户
RUN groupadd -r springboot -g 1000 && \
    useradd -r -u 1000 -g springboot -s /bin/bash -d /app springboot

# 设置安全标签
LABEL security.scan.enabled="true"
LABEL security.compliance="base"

FROM base as runtime

# 设置安全相关的环境变量
ENV JAVA_OPTS="-Xms512m -Xmx1024m \
    -Djava.security.egd=file:/dev/./urandom \
    -Djava.awt.headless=true \
    -Dspring.jmx.enabled=false \
    -Dfile.encoding=UTF-8"

# 使用非root用户
USER springboot

WORKDIR /app

# 复制应用程序
COPY --chown=springboot:springboot target/*.jar app.jar

# 设置文件权限
RUN chmod 500 app.jar && \
    chmod 400 docker-entrypoint.sh health-check.sh

# 安全配置:只读根文件系统
# 注意:需要写入的目录需要单独挂载卷
# docker run --read-only -v /app/logs ...

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD ./health-check.sh

ENTRYPOINT ["./docker-entrypoint.sh"]

多环境部署脚本

bash 复制代码
#!/bin/bash
# multi_env_deploy.sh - 多环境部署脚本

set -euo pipefail

# 配置
ENVIRONMENTS=("dev" "staging" "prod")
REGISTRY="my-registry.example.com"
APP_NAME="springboot-docker-demo"

deploy_to_environment() {
    local env=$1
    local version=$2
    
    echo "🚀 部署到 $env 环境, 版本: $version"
    
    case $env in
        dev)
            deploy_dev $version
            ;;
        staging)
            deploy_staging $version
            ;;
        prod)
            deploy_prod $version
            ;;
        *)
            echo "❌ 未知环境: $env"
            return 1
            ;;
    esac
}

deploy_dev() {
    local version=$1
    
    # 开发环境直接使用最新镜像
    docker-compose -f docker-compose.dev.yml up -d --force-recreate
    
    echo "✅ 开发环境部署完成"
    run_smoke_tests "dev"
}

deploy_staging() {
    local version=$1
    
    # 预生产环境部署
    export VERSION=$version
    docker-compose -f docker-compose.staging.yml up -d --force-recreate
    
    echo "✅ 预生产环境部署完成"
    run_integration_tests
}

deploy_prod() {
    local version=$1
    
    # 生产环境蓝绿部署
    perform_blue_green_deployment $version
    
    echo "✅ 生产环境部署完成"
    run_smoke_tests "prod"
}

perform_blue_green_deployment() {
    local version=$1
    
    # 确定当前运行的颜色(blue 或 green)
    local current_color=$(get_current_deployment_color)
    local new_color=$([ "$current_color" = "blue" ] && echo "green" || echo "blue")
    
    echo "🎨 当前部署颜色: $current_color"
    echo "🎨 新部署颜色: $new_color"
    
    # 启动新颜色的服务
    start_service_with_color $new_color $version
    
    # 等待新服务就绪
    wait_for_service_ready $new_color
    
    # 切换流量
    switch_traffic $new_color
    
    # 停止旧服务
    stop_old_service $current_color
    
    echo "✅ 蓝绿部署完成,当前颜色: $new_color"
}

# 主部署流程
main() {
    local version=${1:-latest}
    
    echo "开始多环境部署流程..."
    echo "应用版本: $version"
    echo "目标环境: ${ENVIRONMENTS[*]}"
    
    for env in "${ENVIRONMENTS[@]}"; do
        echo ""
        echo "=== 部署到 $env 环境 ==="
        
        # 确认部署(生产环境需要确认)
        if [ "$env" = "prod" ]; then
            read -p "确认部署到生产环境? (y/N): " confirm
            if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
                echo "❌ 生产环境部署取消"
                continue
            fi
        fi
        
        # 执行部署
        if deploy_to_environment $env $version; then
            echo "✅ $env 环境部署成功"
        else
            echo "❌ $env 环境部署失败"
            # 可以根据策略决定是否继续部署其他环境
        fi
    done
    
    echo ""
    echo "🎉 多环境部署流程完成"
}

# 执行主函数
main "$@"

总结

通过本文的完整指南,已经掌握了SpringBoot应用Docker容器化的全套技能:

核心收获

  1. 环境标准化: 通过Docker实现开发、测试、生产环境的一致性
  2. 自动化流程: 完整的构建、测试、部署自动化脚本
  3. 生产就绪: 包含监控、日志、安全加固的生产级配置
  4. 最佳实践: 遵循Docker和SpringBoot的最佳实践

关键特性

  • 🔄 持续集成: 自动化构建和测试流程
  • 🐳 容器化: 完整的Dockerfile和多阶段构建
  • 🚀 一键部署: 简单的部署命令即可完成环境部署
  • 📊 全面监控: 集成Prometheus和Grafana监控
  • 🔒 安全加固: 非root用户运行和安全配置

后续

  1. 设置CI/CD流水线实现自动化部署
  2. 配置告警和自动扩缩容
  3. 实施安全扫描和漏洞管理

现在可以告别环境不一致的烦恼,享受Docker容器化带来的便利和可靠性!

相关推荐
星辰徐哥6 小时前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥6 小时前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约6 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee6 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐6 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs6 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
毕设源码_郑学姐6 小时前
计算机毕业设计springboot网络相册设计与实现 基于Spring Boot框架的在线相册管理系统开发与应用 Spring Boot驱动的网络影集设计与实践
spring boot·后端·课程设计
辣机小司6 小时前
【踩坑记录:Spring Boot 配置文件读取值不一致?警惕 YAML 的“八进制陷阱”与 SnakeYAML 版本之谜】
java·spring boot·后端·yaml·踩坑记录
码农阿豪6 小时前
从零到一:Spring Boot快速接入金仓数据库实战
数据库·spring boot·后端
追逐时光者7 小时前
一个基于 .NET 与 Avalonia 构建、面向 TrinityCore 的开源 WoW 数据库编辑器
后端·.net