• GitLab CI/CD 自动化部署-springBoot-demo示例


    CI/CD 的核心概念是持续集成、持续交付和持续部署

    • CI 持续集成(Continuous Integration)
    • CD 持续交付(Continuous Delivery)
    • CD 持续部署(Continuous Deployment)

    GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的Web服务。安装方法是参考GitLab在GitHub上的Wiki页面。
    GitLab 是支持CI/CD的,当前项目使用的gitlab管理的代码,故自己尝试一下 jar的CI/CD。

    参考

    主要参考:GitLab CI/CD 自动化部署入门 ,手把手教你搭建 —— 从安装 Linux 到 GitLab 自动化部署(非常详细)
    Gitlab CI 配置文件 .gitlab-ci.yaml 详解(上)
    GitLab Runner的安装与使用
    部署-gitlab克隆地址踩坑
    Gitlab 安装gitlab-runner踩坑记录
    什么是 CI/CD ?

    操作

    Linux服务器是使用的 公司内网测试服务 centos 7
    gitlab的server端是由同事前不久部署的,版本 14.0.5
    在这里插入图片描述
    下载gitlab-runner,版本15.1.0

     wget https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
     ll
    
    • 1
    • 2

    部署、授权、增加用户、安装、运行

    cp gitlab-runner-linux-amd64 /usr/local/bin/gitlab-runner
     # 分配运行权限
     chmod +x /usr/local/bin/gitlab-runner
     ll /usr/local/bin/
     # 获取版本
     gitlab-runner -v
     # 创建用户
     useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
     # 安装 指定工作目录
     gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
     # 运行 
     gitlab-runner start
     # 注册 runner
    gitlab-runner register
    ####
    # 输入 gitlab 的访问地址
    http://192.168.x.x:x
    # 输入 runner token,把开 http://192.168.x.x:x/admin/runners 页面查看
    xxxxxxx
    # runner 描述,随便填
    构建 runner-001 项目
    # runner tag
    runner-001
    # 输入(选择) shell
    shell
    ####
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    注册完成后,在页面 http://192.168.x.x:x/admin/runners 中查看
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    创建测试项目

    结构如下
    在这里插入图片描述

    gitlab-ci.yml

    # gitlab 持续CI配置
    
    ## 用于docker镜像
    #image: ruby:2.1
    
    ## 用于docker服务
    #services:
    #  - postgres
    
    ## 定义在每个job之前运行的命令
    before_script:
      - echo "----> start"
    
    ## 定义在每个job之后运行的命令
    after_script:
      - echo "----> end"
    
    ## 定义构建变量-全局
    variables:
      MVN_ECHO_1: "开始mvn编译打包"
      RUN_DIR_1: /home/gitlab-runner/server
      JAR_NAME_1: runner-spring-demo-1.0-SNAPSHOT.jar
    ## 定义构建阶段, 默认是(build、test、deploy),可以自定义名称 无限量(不能用 gitlab-ci的保留字)
    stages:
      - build
      - deploy
    cache: # 缓存-全局,job内有就覆盖全局的
      untracked: true # 缓存git中没有被跟踪的文件
      # $CI_JOB_NAME 缓存每个job、$CI_COMMIT_REF_NAME 缓存每个分支、
      # $CI_JOB_NAME/$CI_COMMIT_REF_NAME 缓存每个job且每个分支、$CI_JOB_STAGE/$CI_COMMIT_REF_NAME 缓存每个分支且每个stage
      #key: "$CI_JOB_NAME"
      paths: # 缓存 target 目录,mvn会清理。如果是前端项目 就可以缓存 依赖目录 如 node_modules 可以减少打包时间
        - target/
    # 单独job,名字唯一-拉取项目
    build:
      #cache: # job级别缓存,内容和全局配置一样
      #variables: [] # job级别变量,内容和全局配置一样,[] 是关闭全局变量
      #before_script: # job级别 job之前运行的命令,内容和全局配置一样
      #before_script: # job级别 job之后运行的命令,内容和全局配置一样
      #allow_failure: true # 设置一个job失败的之后并不影响后续的CI组件 默认false
      # 定义何时开始job。可以是on_success(前面stages的所有工作成功时才执行 默认),on_failure(前面stages中任意一个jobs失败后执行),
      # always(无论前面stages中jobs状态如何都执行)或者manual(手动执行)
      #when: on_success
      stage: build # 阶段名称 对应,stages
      tags: # runner 标签(注册runner时设置的,可在 admin->runner中查看)
        - runner_001
      script: # 脚本(执行的命令行)
        - cd ${CI_PROJECT_DIR} # 拉取项目的根目录
        - pwd
        - echo ${MVN_ECHO_1}
        - mvn clean validate  package -Dmaven.test.skip=true
      only: # 定义一列git分支,并为其创建job
        - master # 拉取分支
      #except: # 定义一列git分支,不创建job,和only对应
      artifacts: # 把 dist 的内容传递给下一个阶
        paths:
          - target/
          - archive/
    deploy:
      stage: deploy
      tags:
        - runner_001
      script:
        - echo "开始部署>>jar"
        - mkdir -p ${RUN_DIR_1} # 创建目录,如果存在 不报错
        - /bin/cp -rf ${CI_PROJECT_DIR}/target/${JAR_NAME_1} ${RUN_DIR_1}/${JAR_NAME_1} # 使用系统原生cp强制覆盖 避免交互提示
        - /bin/cp -rf ${CI_PROJECT_DIR}/archive/start_server.sh ${RUN_DIR_1}/start_server.sh # 复制 启动脚本
        - cd ${RUN_DIR_1} # 跳转到 服务目录
        - chmod +x ${RUN_DIR_1}/start_server.sh # 授权运行权限
        - sh ${RUN_DIR_1}/start_server.sh restart # 重启
        - ps -ef | grep ${JAR_NAME_1} # 查看运行状态
        - tail ${RUN_DIR_1}/logs/runner-demo.log # 查看服务运行日志,可以看指定时间日志 info.`date +"%Y-%m-%d"`.0.log
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    start_server.sh

    #!/bin/bash
    
    #jar包路径
    APP_PATH="/home/gitlab-runner/server/"
    JAR_FILE_PATH="${APP_PATH}runner-spring-demo-1.0-SNAPSHOT.jar"
    #环境变量 如果没有可空,dev test prod
    # ACTIVE="-Dspring.profiles.active=test"
    ACTIVE=""
    #web项目检查,是否启动 此路径需要自己创建直接返回 ok,不填写则经过默认时间后认为服务启动
    WEB_CHECK_URL=""
    #console path 数据输出路径,即项目print的内容以及日志sdpt的内容会输出到此文件 /dev/null 为 消失,其他具体文件为输出到指定文件
    CONSOLE_PATH="${APP_PATH}hup.out"
    #预计项目多久启动
    WEB_START_TIME=10
    
    #jvm配置 值依次如下
    #元空间初始大小
    #元空间最大
    #新生代初始空间
    #堆栈初始大小
    #堆栈最大空间
    #垃圾回收器
    #启动模式 使用server
    #设置时区
    #配置文件路径 -Dspring.config.location=${APP_PATH}etc/
    JAVA_OPTS="-XX:MetaspaceSize=256M
    -XX:MaxMetaspaceSize=512M
    -Xms256M
    -Xmx4G
    -XX:+UseG1GC
    -Xss512k
    -server
    -Duser.timezone=GMT+08
    "
    
    #检查服务是否在运行
    is_running(){
      pid=$(ps -ef |grep ${JAR_FILE_PATH}|grep -v grep|awk '{print $2}')
      #如果不存在返回1,存在返回0
      if [ -z "${pid}" ]; then
        return 1
      else
        return 0
      fi
    }
    
    #运行服务,如果服务运行中则进行提示
    start_service(){
      echo `date +"%Y-%m-%d %H:%M:%S"` "开始启动服务..."
      is_running
      if [ $? -eq "0" ]; then
        echo `date +"%Y-%m-%d %H:%M:%S"` "${JAR_FILE_PATH} 已经在运行,线程id ${pid}"
      else
        #启动jar包,并且将输出扔入黑洞(即不保留输出)
        nohup java $JAVA_OPTS -jar $ACTIVE $JAR_FILE_PATH > $CONSOLE_PATH 2>&1 &
        if [ -n "$WEB_CHECK_URL" ]; then
          i=1
          start_ok=0
          while [ $start_ok -eq 0 ]
          do
              if [ $WEB_START_TIME -lt $i ]; then
                start_ok=1
                break
              fi
              back=$(curl -s ${WEB_CHECK_URL})
              if [ "$back" == "ok" ]; then
                start_ok=2
                break
              fi
              let i++
              sleep 1
          done
          if [ $start_ok -eq 2 ]; then
            echo "服务已启动"
          else
            echo `date +"%Y-%m-%d %H:%M:%S"` "服务未在规定时间内正确运行(${WEB_START_TIME}),请检查日志"
          fi
        else
          echo `date +"%Y-%m-%d %H:%M:%S"` "等待${WEB_START_TIME}秒..."
          sleep $WEB_START_TIME
          echo `date +"%Y-%m-%d %H:%M:%S"` "服务已启动"
        fi
      fi
    }
    
    stop_service(){
      echo `date +"%Y-%m-%d %H:%M:%S"` "即将关闭程序..."
      is_running
      if [ $? -eq "1" ]; then
        echo "${JAR_FILE_PATH} 未运行,无需停止"
      else
        echo `date +"%Y-%m-%d %H:%M:%S"` "杀死进程(kill不带-9) ${pid}"
        kill $pid
        echo `date +"%Y-%m-%d %H:%M:%S"` "程序已关闭"
      fi
    }
    
    status(){
      is_running
      if [ $? -eq "0" ]; then
        echo "${JAR_FILE_PATH} 运行中,线程id ${pid}"
      else
        echo "${JAR_FILE_PATH} 未运行"
      fi
    }
    
    #重启服务
    restart_service(){
      echo `date +"%Y-%m-%d %H:%M:%S"` "等待1秒,在执行停止..."
      sleep 1
      stop_service
      echo `date +"%Y-%m-%d %H:%M:%S"` "等待10秒,在执行重启..."
      sleep 10
      start_service
      echo `date +"%Y-%m-%d %H:%M:%S"` "服务重启结束..."
    }
    
    case "$1" in
      "start")
        echo `date +"%Y-%m-%d %H:%M:%S"` "开始执行服务启动..."
        start_service
        ;;
      "stop")
        echo `date +"%Y-%m-%d %H:%M:%S"` "开始执行服务停止..."
        stop_service
        ;;
      "restart")
        echo `date +"%Y-%m-%d %H:%M:%S"` "开始执行服务重启..."
        restart_service
        ;;
      "status")
        status
        ;;
      *)
        echo "命令为 start|stop|restart|status"
        exit 0
        ;;
    esac
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139

    pom

    
    <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.0modelVersion>
    
        <groupId>cn.zy.demo.runnergroupId>
        <artifactId>runner-spring-demoartifactId>
        <version>1.0-SNAPSHOTversion>
        
        <parent>
            <artifactId>spring-boot-starter-parentartifactId>
            <groupId>org.springframework.bootgroupId>
            <version>2.3.1.RELEASEversion>
            <relativePath/>
        parent>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
            <java.version>1.8java.version>
            <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
            <skipTests>trueskipTests>
    
            <spring-boot.version>2.3.1.RELEASEspring-boot.version>
        properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starterartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-validationartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-configuration-processorartifactId>
                <optional>trueoptional>
            dependency>
        dependencies>
        <build>
            <finalName>${project.artifactId}-${project.version}finalName>
            <plugins>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackagegoal>
                            goals>
                        execution>
                    executions>
                plugin>
                <plugin>
                    <groupId>org.apache.maven.pluginsgroupId>
                    <artifactId>maven-jar-pluginartifactId>
                    <executions>
                        <execution>
                            <id>default-jarid>
                            <phase>packagephase>
                            <goals>
                                <goal>jargoal>
                            goals>
                        execution>
                    executions>
                plugin>
                <plugin>
                    <groupId>org.apache.maven.pluginsgroupId>
                    <artifactId>maven-deploy-pluginartifactId>
                    <configuration>
                        <skip>trueskip>
                    configuration>
                plugin>
            plugins>
        build>
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    yml

    server:
      port: 9980
      version: 2022072001
      servlet:
        context-path: /runner-demo/
    spring:
      jackson:
        time-zone: GMT+8
        date-format: yyyy-MM-dd HH:mm:ss
    logging:
      file:
        name: ./logs/runner-demo.log
      pattern:
        console: '%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    java

    启动类

    import cn.zy.demo.runner.util.EnvUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * 启动类
     *
     * @author z.y.l
     * @version v1.0
     * @date 2022/7/20
     */
    @SpringBootApplication
    public class RunnerSpringDemoApplication {
        private static final Logger log = LoggerFactory.getLogger(RunnerSpringDemoApplication.class);
        public static void main(String[] args) {
            SpringApplication.run(RunnerSpringDemoApplication.class,args);
            String port = EnvUtil.getStr("server.port"),
                    path = EnvUtil.getStr("server.servlet.context-path");
            log.info("http://127.0.0.1:{}{}",port,path);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    工具类

    import org.springframework.context.EnvironmentAware;
    import org.springframework.core.env.Environment;
    import org.springframework.stereotype.Component;
    
    import javax.validation.constraints.NotNull;
    
    /**
     * EnvUtil 类说明:
     *
     * @author z.y.l
     * @version v1.0
     * @date 2022/7/20
     */
    @Component
    public class EnvUtil implements EnvironmentAware {
        private static Environment env;
        @Override
        public void setEnvironment(@NotNull Environment environment) {
            env = environment;
        }
        public static String getStr(String key){
            return env.getProperty(key);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    测试接口

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Date;
    import java.util.Map;
    import java.util.TreeMap;
    
    /**
     * RootController 类说明:
     *
     * @author z.y.l
     * @version v1.0
     * @date 2022/7/20
     */
    @RestController
    @RequestMapping("/")
    public class RootController {
        private static final Logger logger = LoggerFactory.getLogger(RootController.class);
        private static final Date RUN_TIME = new Date();
        @Value("${server.version}")
        private String version;
        @RequestMapping
        public Map<String, Object> index(){
            Map<String, Object> map  = new TreeMap<>();
            map.put("now-time", new Date());
            map.put("run-time", RUN_TIME);
            map.put("version", version);
            logger.info("请求,{}",map);
            return map;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    测试CI CD

    提交代码,登录 页面 http://192.168.x.x:x/admin/jobs,就可以看到触发的自动job。
    可以看到定义的两个阶段job串行执行了,status 状态大概是:等待、执行中、已失败、已跳过等。点击 status状态,就可以看到日志了
    在这里插入图片描述

    大概分为几个运行阶段

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    mvn编译的日志

    在这里插入图片描述

    第二个job

    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

    部署的日志

    在这里插入图片描述

    失败的情况

    在这里插入图片描述

    跳过的没有日志

    因为依赖的上个job执行失败,就会跳过,也和yml中的配置有关系
    在这里插入图片描述

    提交项目,自动构建,查看发现失败

    构建失败的原因是gitlab的克隆路径不对,因为gitlab是公司同事维护的,并且是在docker部署。联系同事一起解决问题
    在这里插入图片描述
    在这里插入图片描述
    修复后
    在这里插入图片描述
    在这里插入图片描述

    问题-部署失败-git版本低问题

    报错

    Running with gitlab-runner 15.1.0 (76984217)
      on 构建 runner001 项目 xymGSfhp
    Preparing the "shell" executor
    00:00
    Using Shell executor...
    Preparing environment
    00:00
    Running on localhost.localdomain...
    Getting source from Git repository
    00:01
    Fetching changes with git depth set to 50...
    重新初始化现存的 Git 版本库于 /home/gitlab-runner/builds/xymGSfhp/0/****/runner-spring-demo/.git/
    fatal: git fetch-pack: expected shallow list
    fatal: The remote end hung up unexpectedly
    ERROR: Job failed: exit status 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    解决方式

    # git版本太低 要升级
    git --version
    #git version 1.8.3.1
    #安装源
    yum install http://opensource.wandisco.com/centos/7/git/x86_64/wandisco-git-release-7-2.noarch.rpm
    #安装git
    #yum install git
    #更新git
    yum update git
    #...
    git --version
    #git version 2.31.1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    END

    这只是后端的部署,前端部署 在cp文件到nginx下面就可以了,命令更少点。这只是个人的操作记录,请根据实际情况自行判断。

  • 相关阅读:
    CTO:我不建议你在 Docker 中跑 MySQL
    SLAM从入门到精通(ROS网络通信)
    简述现代加油站的智能防雷设计及其解决措施
    如何保卫您的网站:解决DDoS攻击与CC攻击
    Python基础之装饰器
    数据分析之Numpy、Pandas和Sklearn
    jira自定义字段userpicker类型不能用
    csgo 闪退
    springboot+mybatisplus前后端分离——纯后端(万字)开发流程
    2023年9月青少年机器人技术(三级)等级考试试卷-理论综合
  • 原文地址:https://blog.csdn.net/privateobject/article/details/126265188