SpringBoot服务优雅停机

文章目录

  • [Spring Boot 服务优雅停机完全指南](#Spring Boot 服务优雅停机完全指南)
    • 一、为什么需要优雅停机
      • [1.1 问题背景](#1.1 问题背景)
      • [1.2 不优雅停机的后果](#1.2 不优雅停机的后果)
    • [2. Spring Boot 默认的停机行为](#2. Spring Boot 默认的停机行为)
      • [2.1 快速关闭(Immediate Shutdown)](#2.1 快速关闭(Immediate Shutdown))
      • [2.2 启用优雅停机](#2.2 启用优雅停机)
    • [3. 核心原理与源码解读](#3. 核心原理与源码解读)
      • [3.1 Spring Boot 优雅停机架构](#3.1 Spring Boot 优雅停机架构)
      • 3.2 源码解读:SpringApplication#run
      • [3.3 源码解读:Tomcat 优雅停机](#3.3 源码解读:Tomcat 优雅停机)
      • [3.4 源码解读:Spring LifecycleProcessor](#3.4 源码解读:Spring LifecycleProcessor)
      • [3.5 停机阶段详细流程图](#3.5 停机阶段详细流程图)
    • [4. 优雅停机最佳实践](#4. 优雅停机最佳实践)
      • [4.1 服务注册中心下线](#4.1 服务注册中心下线)
      • [4.2 线程池优雅关闭](#4.2 线程池优雅关闭)
      • [4.3 数据库连接池配置](#4.3 数据库连接池配置)
      • [4.4 自定义组件的优雅停机](#4.4 自定义组件的优雅停机)
      • [4.5 异步任务优雅处理](#4.5 异步任务优雅处理)
    • [5. Spring Boot 2.3+ 增强的优雅停机](#5. Spring Boot 2.3+ 增强的优雅停机)
      • [5.1 新特性概览](#5.1 新特性概览)
      • [5.2 配置对比](#5.2 配置对比)
      • [5.3 Actuator 安全关闭](#5.3 Actuator 安全关闭)
    • [6. 生产环境配置实战](#6. 生产环境配置实战)
      • [6.1 完整配置示例](#6.1 完整配置示例)
      • [6.2 K8s 部署配置](#6.2 K8s 部署配置)
      • [6.3 健康检查配置](#6.3 健康检查配置)
    • [7. 总结](#7. 总结)
      • [7.1 核心要点回顾](#7.1 核心要点回顾)
      • [7.2 避坑指南](#7.2 避坑指南)
      • [7.3 监控指标](#7.3 监控指标)

Spring Boot 服务优雅停机完全指南

优雅停机是,生产环境服务治理的核心能力之一。本文从核心原理到源码解读,系统讲解 Spring Boot 优雅停机的完整技术体系。


一、为什么需要优雅停机

1.1 问题背景

在生产环境中,服务经常需要:

  • 滚动更新:K8s deployments 滚动更新时,旧 Pod 被终止,新 Pod 启动
  • 定时维护:凌晨的业务低峰期进行系统升级
  • 灰度发布:流量切换过程中的服务实例下线
  • 故障恢复:实例异常时的自动替换

1.2 不优雅停机的后果

复制代码
流量损失图:
+-----------------------------------------------------------------------+
|  请求 ---------------------------------------------------------------->   |
|                  |                    |                               |
|              正常处理              正在处理                          |
|              #######                *******                          |
|              |                    |                                  |
|          Terminate            Connection Reset                     |
|          |                    |                                     |
|     +----+-----+          +----+-----+                               |
|     |  500    |          |  504    |                                |
|     |  Error  |          |  Timeout                               |
|     +---------+          +---------+                               |
+-----------------------------------------------------------------------+

具体问题

问题类型 具体表现 影响
请求丢失 正在处理的业务请求被强制中断 数据不一致
拒绝服务 新请求被直接拒绝 短时不可用
连接断开 WebSocket/TCP 连接强制关闭 体感中断
资源泄漏 数据库连接未释放 后续启动失败
任务中断 异步任务/定时任务被强制终止 业务逻辑错误

2. Spring Boot 默认的停机行为

2.1 快速关闭(Immediate Shutdown)

Spring Boot 默认使用 快速关闭 模式:

properties 复制代码
# 默认配置
server.shutdown=immediate  # 立即关闭

行为时序

复制代码
时间线 ──────────────────────────────────────────────────►

0ms        10ms        20ms        30ms        40ms
  │          │          │          │          │
  │──────────│──────────│──────────│──────────│
  │          │          │          │          │
  ▼          ▼          ▼          ▼          ▼
┌─────────────────────────────────────────────────────────────┐
│ Tomcat  │  停止   │          │          │                    │
│ Graceful│ 接受    │  超时    │  强制    │                    │
│ Wait    │  新请求 │  等待   │  关闭   │                    │
└─────────────────────────────────────────────────────────────┘

2.2 启用优雅停机

yaml 复制代码
# application.yml
server:
  shutdown: graceful  # 启用优雅停机
  
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s  # 停机阶段超时时间

3. 核心原理与源码解读

3.1 Spring Boot 优雅停机架构

复制代码
┌────────────────────────────────────────────────────────────────┐
│                    Spring Boot 优雅停机架构                      │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │
│  │  Shutdown   │──►│  Lifecycle   │──►│  Tomcat      │       │
│  │  Hook       │    │  Manager    │    │  Connector  │       │
│  └──────────────┘    └──────────────┘    └──────────────┘       │
│         │                   │                   │                  │
│         ▼                   ▼                   ▼                  │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │
│  │  @PreDestroy│    │  Bean       │    │  Pool      │       │
│  │  Filters   │    │  Destroy    │    │  Sockets  │       │
│  └──────────────┘    └──────────────┘    └──────────────┘       │
│                                                                │
└────────────────────────────────────────────────────────────────┘

3.2 源码解读:SpringApplication#run

核心源码位置:Spring Boot 启动流程中的优雅退出机制。

java 复制代码
// Spring Boot 2.x 核心源码
// 文件:spring-boot project's SpringApplication.java

public ConfigurableApplicationContext run(String... args) {
    // ... 启动逻辑
    
    // 1. 注册 JVM 关闭钩子
    this.lifecycleResources.registerClosedHttpServer(() -> {
        // 执行优雅停机
        SpringApplication.this.exitCode = 0;
        getShutdownHandler().handleProcessExit(0);
    });
    
    // 2. 等待优雅停机完成
    // Reference: DefaultLifecycleProcessor
}

3.3 源码解读:Tomcat 优雅停机

核心源码位置org.apache.catalina.core.StandardService

java 复制代码
// Tomcat 源码核心分析
// 文件:apache-tomcat's StandardService.java

@Override
public void stopInternal() {
    // 阶段1: 停止接收新请求
    this.serverSocket.close();  // 关闭监听Socket
    
    // 阶段2: 等待活跃请求处理完成
    // Reference: NioEndpoint.java
    for (Connector connector : this.connectors) {
        connector.pause();  // 暂停Connector
        // 等待请求超时
        long timeout = getStopTimoeut();
        boolean timedOut = waitForActiveRequests(timeout, TimeUnit.SECONDS);
        if (timedOut) {
            // 超时则强制关闭
            forceStopInternal();
        }
    }
}

private boolean waitForActiveRequests(long timeout, TimeUnit unit) {
    // 核心逻辑:等待线程池中的任务完成
    Executor executor = getConnector().getProtocolHandler().getExecutor();
    if (executor instanceof ThreadPoolExecutor) {
        ThreadPoolExecutor threadPool = (ThreadPoolExecutor) executor;
        // 拒绝新任务提交
        threadPool.shutdown();  
        // 等待现有任务完成
        return threadPool.awaitTermination(timeout, unit);
    }
    return true;
}

3.4 源码解读:Spring LifecycleProcessor

java 复制代码
// Spring Framework 核心源码
// 文件:org.springframework.context.support.DefaultLifecycleProcessor

public class DefaultLifecycleProcessor implements LifecycleProcessor {
    
    // 执行 Bean 的优雅停机
    @Override
    public void onClose() {
        // 按 SmartLifeCycle.getPhase() 排序
        Map<Integer, List<Lifecycle>> phases = new TreeMap<>();
        
        // 按阶段分组 Beans
        this.beanFactory.getBeansOfType(Lifecycle.class, true, false)
            .forEach((name, bean) -> phases
                .computeIfAbsent(getPhase(bean), k -> new ArrayList<>())
                .add(bean));
        
        // 按阶段依次停止(反向顺序:先停止依赖者)
        for (List<Lifecycle> beans : phases.descendingMap().values()) {
            beans.forEach(bean -> {
                try {
                    bean.stop();
                } catch (Throwable ex) {
                    // 记录异常但继续
                }
            });
        }
    }
    
    private int getPhase(Lifecycle bean) {
        if (bean instanceof SmartLifecycle) {
            return ((SmartLifecycle) bean).getPhase();
        }
        // 默认相位:Integer.MAX_VALUE(最后停止)
        return DEFAULT_PHASE;
    }
}

3.5 停机阶段详细流程图

复制代码
Spring Boot 优雅停机完整流程
═══════════════════════════════════════════════════════════════════════

         ╔═══════════════╗
         ║ SIGTERM /     ║
         ║ /shutdown    ║
         ╚═════╤══════════╝
               │
               ▼
┌──────────────────────────────────────────────────────────────────┐
│ Phase 1: 停止接受新请求                                              │
├──────────────────────────────────────────────────────────────────┤
│  • Tomcat 关闭 ServerSocket                                        │
│  • 设置 Connector 状态为 PAUSED                                    │
│  • LoadBalancer 开始摘除流量                                      │
│                                                                  │
│  【关键代码】connector.pause()                                    │
└──────────────────────────────────────────────────────────────────┘
               │
               ▼
┌────────────────────────────────��─��───────────────────────────────┐
│ Phase 2: 等待正在处理的请求                                        │
├──────────────────────────────────────────────────────────────────┤
│  • 等待 ThreadPoolExecutor 中的任务完成                            │
│  • 超时时间可配置:spring.lifecycle.timeout-per-shutdown-phase   │
│  • 默认:30秒                                                     │
│                                                                  │
│  【关键代码】threadPool.awaitTermination()                       │
└──────────────────────────────────────────────────────────────────┘
               │
               ├──────────────────┬──────────────────────────────────┐
               │                  │                                  │
               ▼                  ▼                                  │
      ┌───��─────────────┐   ┌─────────────────┐                     │
      │  等待超时完成   │   │    超时强制终止 │                     │
      ├─────────────────┤   ├─────────────────┤                     │
      │ 所有请求处理完 │   │ 调用 shutdownNow│                     │
      │  进入下一阶段 │   │  线程中断退出    │                     │
      └────────┬────────┘   └────────┬────────┘                     │
               │                     │                               │
               └──────────┬──────────┘                               │
                          ▼                                          │
┌──────────────────────────────────────────────────────────────────┐
│ Phase 3: 执行 Bean 销毁                                            │
├──────────────────────────────────────────────────────────────────┤
│  @PreDestroy 标注的方法执行                                       │
│  @Bean(destroyMethod="xxx") 指定的方法                           │
│  DisposableBean.destroy()                                         │
│  Closeable.close()                                                │
│                                                                  │
│  按 SmartLifecycle.getPhase() 排序执行                           │
└──────────────────────────────────────────────────────────────────┘
               │
               ▼
┌──────────────────────────────────────────────────────────────────┐
│ Phase 4: 关闭基础设施                                             │
├──────────────────────────────────────────────────────────────────┤
│  • 关闭数据库连接池 HikariCP                                     │
│  • 关闭 Redis 连接池                                              │
│  • 关闭消息队列连接                                               │
│  • 注销服务注册 (Nacos/Zookeeper)                               │
│  • 关闭 HTTP Client 连接池                                      │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
               │
               ▼
┌──────────────────────────────────────────────────────────────────┐
│ Phase 5: JVM 退出                                                 │
├──────────────────────────────────────────────────────────────────┤
│  • 调用 System.exit(0)                                           │
│  • 进程终止                                                       ��
��──────────────────────────────────────────────────────────────────┘

4. 优雅停机最佳实践

4.1 服务注册中心下线

核心要点:在服务停止前主动从注册中心注销服务。

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lifecycle.annotation.Stopped;

@Configuration
public class ServiceDeregistrationConfig {
    
    @Autowired
    private NacosServiceRegistry nacosServiceRegistry;
    
    @Autowired
    private Registration registration;
    
    /**
     * 在 Spring 容器关闭时执行
     * 执行时机:在 Bean 销毁之前
     */
    @Stopped
    public void onServiceStop() {
        String serviceId = registration.getServiceId();
        String instanceId = registration.getInstanceId();
        
        log.info("开始注销服务: serviceId={}, instanceId={}", 
                serviceId, instanceId);
        
        // 立即摘除流量:不等待
        nacosServiceRegistry.deregister(registration);
        
        log.info("服务注销完成: {}", serviceId);
    }
}

4.2 线程池优雅关闭

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

@Configuration
public class ThreadPoolConfig {
    
    @Bean(name = "businessExecutor", destroyMethod = "shutdownGracefully")
    public ExecutorService businessExecutor() {
        ThreadFactory threadFactory = new ThreadFactoryBuilder()
            .setNameFormat("business-pool-%d")
            .setDaemon(false)  // 非守护线程,确保等待任务完成
            .build();
        
        return new ThreadPoolExecutor(
            4,                                      // corePoolSize
            8,                                      // maximumPoolSize
            60L, TimeUnit.SECONDS,                   // keepAliveTime
            new LinkedBlockingQueue<>(100),          // 队列容量
            threadFactory,
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );
    }
}

关键说明

  • 使用 shutdownGracefully() 而非 shutdownNow()
  • 设置 allowCoreThreadTimeOut(true) 使核心线程也能超时退出

4.3 数据库连接池配置

yaml 复制代码
# application.yml
spring:
  datasource:
    hikari:
      maximum-pool-size: 10
      minimum-idle: 5
      # 关键:连接池关闭超时
      max-lifetime: 1800000    # 30分钟
      connection-timeout: 30000
      # 优雅停机相关
      leak-detection-threshold: 60000  # 泄漏检测

4.4 自定义组件的优雅停机

java 复制代码
import javax.annotation.PreDestroy;

@Component
public class CustomComponent implements AutoCloseable {
    
    private volatile boolean running = true;
    private ExecutorService executor;
    
    public CustomComponent() {
        this.executor = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "custom-component");
            t.setDaemon(false);
            return t;
        });
    }
    
    public void process() {
        // 业务处理逻辑
    }
    
    /**
     * 关键:在容器关闭时会被调用
     * 注意:@PreDestroy 执行在 destroyMethod 之前
     */
    @PreDestroy
    public void shutdown() {
        log.info("收到停止信号,准备关闭...");
        running = false;
        
        // 设置 interrupt 标志
        executor.shutdown();
        try {
            if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
    
    /**
     * AutoCloseable.close() 
     * 通过 destroyMethod="close" 指定
     */
    @Override
    public void close() {
        // 清理资源
    }
}

4.5 异步任务优雅处理

java 复制代码
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class AsyncTaskProcessor {
    
    @Autowired
    private TaskExecutor taskExecutor;
    
    private final Map<String, CompletableFuture> futures = new ConcurrentHashMap<>();
    
    @Async("businessExecutor")
    public CompletableFuture<Result> submitTask(String taskId, Task task) {
        CompletableFuture<Result> future = CompletableFuture.supplyAsync(() -> {
            try {
                // 执行业务逻辑
                return processTask(task);
            } finally {
                // 任务完成后移除
                futures.remove(taskId);
            }
        }, taskExecutor);
        
        futures.put(taskId, future);
        return future;
    }
    
    /**
     * 检查并等待所有任务完成
     */
    public boolean awaitAllTasks(long timeout, TimeUnit unit) {
        if (futures.isEmpty()) {
            return true;
        }
        
        CompletableFuture<?>[] array = futures.values().toArray(new CompletableFuture[0]);
        try {
            CompletableFuture.allOf(array).get(timeout, unit);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

5. Spring Boot 2.3+ 增强的优雅停机

5.1 新特性概览

Spring Boot 2.3 引入了增强的优雅停机支持:

特性 Spring Boot 2.3+ 之前版本
graceful shutdown 内置支持 需要自定义
配置项 server.shutdown
连接池关闭 自动等待 需手动配置
Actuator shutdown 更安全 需额外配置

5.2 配置对比

yaml 复制代码
# Spring Boot 2.3+
server:
  shutdown: graceful            # 启用优雅停机
  
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s   # 停机阶段超时
    
# Spring Boot 2.3 之前的版本
# 需要手动实现 AbstractReactiveWebServerApplicationContext
# 或使用 Tomcat 的配置

5.3 Actuator 安全关闭

yaml 复制代码
# pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,shutdown
      base-path: /actuator
  endpoint:
    shutdown:
      enabled: true          # 启用 shutdown 端点
      
# 安全配置(必须)
spring.security.user.name=admin
spring.security.user.password=${SHUTDOWN_PASSWORD}

调用方式

bash 复制代码
# POST 请求关闭服务
curl -X POST "http://localhost:8080/actuator/shutdown" \
     -u admin:your_password \
     -H "Content-Type: application/json"

6. 生产环境配置实战

6.1 完整配置示例

yaml 复制代码
# application.yml - 生产环境完整配置
server:
  shutdown: graceful
  tomcat:
    # 等待请求超时时间
    connection-timeout: 60s
    # 最大连接数
    max-connections: 20000
    accept-count: 100
    
spring:
  lifecycle:
    timeout-per-shutdown-phase: 60s
    
# 服务注册
spring.cloud.nacos:
  discovery:
    instance-id: ${spring.application.name}-${server.port}
    ephemeral: false          # 关闭后保留实例信息
    ip-delete-interval: 5000  # 5秒删除过期实例
    
# 数据库连接池
spring.datasource:
  hikari:
    maximum-pool-size: 20
    minimum-idle: 5
    connection-timeout: 30000
    max-lifetime: 1800000
    leak-detection-threshold: 60000
    
# Redis 连接池
spring.redis:
  lettuce:
    pool:
      max-active: 16
      max-idle: 8
      min-idle: 4
      max-wait: 3000ms
      
# 线程池
business:
  executor:
    core-pool-size: 10
    max-pool-size: 50
    queue-capacity: 500
    keep-alive-seconds: 60
    thread-name-prefix: business-
    
# 日志
logging:
  level:
    root: INFO
    org.springframework.lifecycle: DEBUG
    org.apache.catalina: DEBUG

6.2 K8s 部署配置

yaml 复制代码
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0  # 关键:保持全部可用
  template:
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        ports:
        - containerPort: 8080
        # 优雅停机相关配置
        env:
        - name: JAVA_OPTS
          value: "-Xms512m -Xmx512m"
        # 预停机钩子(可选)
        lifecycle:
          preStop:
            exec:
              command:
              - /bin/sh
              - -c
              - "sleep 10"
---
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 8080
  type: ClusterIP

关键配置说明

yaml 复制代码
# preStop 延迟原理:
# 1. K8s 发送 SIGTERM
# 2. 业务容器收到信号,开始优雅停机
# 3. preStop sleep 10s,给业务充足时间
# 4. 期间 LoadBalancer 摘除流量
# 5. sleep 结束后容器才被终止

6.3 健康检查配置

java 复制代码
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class ShutdownHealthIndicator implements HealthIndicator {
    
    private final AtomicBoolean shuttingDown = new AtomicBoolean(false);
    
    public void setShuttingDown() {
        shuttingDown.set(true);
    }
    
    @Override
    public Health health() {
        if (shuttingDown.get()) {
            return Health.down()
                .withDetail("reason", "Service is shutting down")
                .build();
        }
        return Health.up().build();
    }
}

7. 总结

7.1 核心要点回顾

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    优雅停机核心要素                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1️⃣ 配置启用                                                    │
│     server.shutdown = graceful                                 │
│     spring.lifecycle.timeout-per-shutdown-phase = 30s           │
│                                                                  │
│  2️⃣ 基础设施关闭顺序                                           │
│     服务注册 → 线程池 → 连接池 → Bean销毁 → JVM退出            │
│                                                                  │
│  3️⃣ 关键实现点                                                  │
│     • Nacos/Zookeeper 服务下线                                   │
│     • 使用 awaitTermination() 等待��步��务                      │
│     • 配置连接池超时                                             │
│     • @PreDestroy 清理资源                                       │
│                                                                  │
│  4️⃣ K8s 配合                                                   │
│     • maxUnavailable: 0                                         │
│     • preStop sleep 延迟                                          │
│     • 添加健康探针                                                │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

7.2 避坑指南

陷阱 后果 解决方案
使用 shutdownNow() 任务被强制终止 使用 shutdown() + awaitTermination()
数据库连接池未关闭 连接泄漏 显式配置 close Method
异步任务未等待 请求丢失 使用 CompletableFuture 管理
K8s 探针未配置 流量未摘除 添加 /actuator/health 探针
超时时间太短 任务被强制中断 根据业务调整 timeout-per-shutdown-phase

7.3 监控指标

推荐添加以下监控指标:

复制代码
# 优雅停机相关 metrics
spring_lifecycle_shutdown_phase_duration_seconds
tomcat_connections_active
tomcat_threads_busy
hikari_connections_active
business_tasks_queued


💡 提示 :实际生产环境中,建议进行真实的优雅停机演练,验证配置是否生效。可以利用 curl -X POST localhost:8080/actuator/shutdown 模拟关闭,监控系统行为。

相关推荐
郑洁文9 小时前
旅游景点推荐系统的设计与实现
springboot·毕设·旅游系统·旅游景点推荐系统
ANnianStriver14 小时前
PetLumina-AI 驱动的宠物生活管理平台
java·生活·vue3·springboot·ai编程·宠物·全栈开发
YDS82916 小时前
DeepSeek RAG&MCP + Agent智能体项目 —— 集成ELK日志管理系统和Prometheus监控系统
java·elk·ai·springboot·agent·prometheus·deepseek
极光代码工作室2 天前
基于SpringBoot的任务管理系统
java·springboot·web开发·后端开发
huipeng9263 天前
企业级微服务开发实战(二):微服务基础设施搭建与中间件部署
java·redis·mysql·spring cloud·微服务·nacos·rabbitmq
杭州杭州杭州3 天前
瑞吉外卖项目
springboot
逍遥德3 天前
Java编程高频的“技术点”-03:“下划线命名”参数,后端用“驼峰命名“接收
java·后端·springboot
弹简特3 天前
【Java项目-轻聊】08-用户管理模块-实现获取用户信息+头像上传+显示头像
java·开发语言·springboot
行者-全栈开发6 天前
SpringBoot CI/CD 流水线实战|Jenkins+GitLab CI,从手动到自动化交付
ci/cd·jenkins·springboot·devops·自动化部署·gitlab ci
华大哥8 天前
前后端分离实现五级行政区划树形菜单及设备查询管理
sqlite·vue·springboot