zoukankan      html  css  js  c++  java
  • centos7安装jenkins以及结合gitlab实现自动部署

      参考网站:https://www.jenkins.io/zh/

      下面的练习基于jenkins版本: 2.249.1

    1. 简介

      Jenkins是开源CI&CD软件领导者, 提供超过1000个插件来支持构建、部署、自动化, 满足任何项目的需要。

      jenkins有一系列的插件,可以用于拉取代码、打包(javavue)项目、以及传输文件之后shell脚本等操作。这些插件组合起来可以满足大部分自动部署需求。而且jenkins提供了获取构建信息等的接口,可以用客户端获取到jenkins构建信息等操作。

     2.安装

      当然如果采用docker安装的话会非常简单。

     1. 安装JDK

     直接yum安装 openjdk

    yum install java-1.8.0-openjdk*

     2. 下载jenkins

    http://mirrors.jenkins.io/war-stable/latest/jenkins.war

    如下:

    [root@localhost jenkins]# ls
    jenkins.war

     3. 启动jenkins

    [root@localhost jenkins]# java -jar jenkins.war --httpPort=8088
    Running from: /opt/jenkins/jenkins.war
    webroot: $user.home/.jenkins
    2020-09-13 16:50:16.155+0000 [id=1]     INFO    org.eclipse.jetty.util.log.Log#initialized: Logging initialized @7478ms to org.eclipse.jetty.util.log.JavaUtilLog
    2020-09-13 16:50:18.705+0000 [id=1]     INFO    winstone.Logger#logInternal: Beginning extraction from war file
    2020-09-13 16:50:20.551+0000 [id=1]     WARNING o.e.j.s.handler.ContextHandler#setContextPath: Empty contextPath
    2020-09-13 16:50:21.299+0000 [id=1]     INFO    org.eclipse.jetty.server.Server#doStart: jetty-9.4.30.v20200611; built: 2020-06-11T12:34:51.929Z; git: 271836e4c1f4612f12b7bb13ef5a92a927634b0d; jvm 1.8.0_262-b10
    2020-09-13 16:50:26.740+0000 [id=1]     INFO    o.e.j.w.StandardDescriptorProcessor#visitServlet: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
    2020-09-13 16:50:28.164+0000 [id=1]     INFO    o.e.j.s.s.DefaultSessionIdManager#doStart: DefaultSessionIdManager workerName=node0
    2020-09-13 16:50:28.168+0000 [id=1]     INFO    o.e.j.s.s.DefaultSessionIdManager#doStart: No SessionScavenger set, using defaults
    2020-09-13 16:50:28.254+0000 [id=1]     INFO    o.e.j.server.session.HouseKeeper#startScavenging: node0 Scavenging every 600000ms
    2020-09-13 16:50:36.340+0000 [id=1]     INFO    hudson.WebAppMain#contextInitialized: Jenkins home directory: /root/.jenkins found at: $user.home/.jenkins
    2020-09-13 16:50:38.555+0000 [id=1]     INFO    o.e.j.s.handler.ContextHandler#doStart: Started w.@3e78b6a5{Jenkins v2.249.1,/,file:///root/.jenkins/war/,AVAILABLE}{/root/.jenkins/war}
    2020-09-13 16:50:39.332+0000 [id=1]     INFO    o.e.j.server.AbstractConnector#doStart: Started ServerConnector@2d554825{HTTP/1.1, (http/1.1)}{0.0.0.0:8088}
    2020-09-13 16:50:39.333+0000 [id=1]     INFO    org.eclipse.jetty.server.Server#doStart: Started @30672ms
    2020-09-13 16:50:39.387+0000 [id=20]    INFO    winstone.Logger#logInternal: Winstone Servlet Engine running: controlPort=disabled
    2020-09-13 16:50:59.598+0000 [id=28]    INFO    jenkins.InitReactorRunner$1#onAttained: Started initialization
    2020-09-13 16:51:00.488+0000 [id=29]    INFO    jenkins.InitReactorRunner$1#onAttained: Listed all plugins
    2020-09-13 16:51:30.556+0000 [id=28]    INFO    jenkins.InitReactorRunner$1#onAttained: Prepared all plugins
    2020-09-13 16:51:30.629+0000 [id=28]    INFO    jenkins.InitReactorRunner$1#onAttained: Started all plugins
    2020-09-13 16:51:30.777+0000 [id=29]    INFO    jenkins.InitReactorRunner$1#onAttained: Augmented all extensions
    2020-09-13 16:51:56.187+0000 [id=28]    INFO    jenkins.InitReactorRunner$1#onAttained: System config loaded
    2020-09-13 16:51:56.188+0000 [id=28]    INFO    jenkins.InitReactorRunner$1#onAttained: System config adapted
    2020-09-13 16:51:56.190+0000 [id=28]    INFO    jenkins.InitReactorRunner$1#onAttained: Loaded all jobs
    2020-09-13 16:51:56.474+0000 [id=29]    INFO    jenkins.InitReactorRunner$1#onAttained: Configuration for all jobs updated
    2020-09-13 16:52:03.166+0000 [id=40]    INFO    hudson.model.AsyncPeriodicWork#lambda$doRun$0: Started Download metadata
    2020-09-13 16:52:03.660+0000 [id=40]    INFO    hudson.util.Retrier#start: Attempt #1 to do the action check updates server
    2020-09-13 16:52:16.771+0000 [id=28]    INFO    o.s.c.s.AbstractApplicationContext#prepareRefresh: Refreshing org.springframework.web.context.support.StaticWebApplicationContext@2bc0226: display name [Root WebApplicationContext]; startup date [Sun Sep 13 12:52:16 EDT 2020]; root of context hierarchy
    2020-09-13 16:52:16.773+0000 [id=28]    INFO    o.s.c.s.AbstractApplicationContext#obtainFreshBeanFactory: Bean factory for application context [org.springframework.web.context.support.StaticWebApplicationContext@2bc0226]: org.springframework.beans.factory.support.DefaultListableBeanFactory@fc9646
    2020-09-13 16:52:16.994+0000 [id=28]    INFO    o.s.b.f.s.DefaultListableBeanFactory#preInstantiateSingletons: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@fc9646: defining beans [authenticationManager]; root of factory hierarchy
    2020-09-13 16:52:23.053+0000 [id=28]    INFO    o.s.c.s.AbstractApplicationContext#prepareRefresh: Refreshing org.springframework.web.context.support.StaticWebApplicationContext@58dd076f: display name [Root WebApplicationContext]; startup date [Sun Sep 13 12:52:23 EDT 2020]; root of context hierarchy
    2020-09-13 16:52:23.054+0000 [id=28]    INFO    o.s.c.s.AbstractApplicationContext#obtainFreshBeanFactory: Bean factory for application context [org.springframework.web.context.support.StaticWebApplicationContext@58dd076f]: org.springframework.beans.factory.support.DefaultListableBeanFactory@25f19f6c
    2020-09-13 16:52:23.077+0000 [id=28]    INFO    o.s.b.f.s.DefaultListableBeanFactory#preInstantiateSingletons: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@25f19f6c: defining beans [filter,legacy]; root of factory hierarchy
    2020-09-13 16:52:26.119+0000 [id=28]    INFO    jenkins.install.SetupWizard#init: 
    
    *************************************************************
    *************************************************************
    *************************************************************
    
    Jenkins initial setup is required. An admin user has been created and a password generated.
    Please use the following password to proceed to installation:
    
    9f01f93af3fa4695ae8aabd0b00cb228
    
    This may also be found at: /root/.jenkins/secrets/initialAdminPassword
    
    *************************************************************
    *************************************************************
    *************************************************************

    4. 浏览器访问 8088端口

     然后用上面生成的密码进行登录(如果没有可以到上面提示的文件中查找密码),登录之后查看信息如下:

       选择第二个指定的插件进行安装。之后不选择安装插件。

      进入主页面:

     5. 安装插件

    Jenkins >> Manage Jenkins >> Manage Plugins (插件管理也是在这个选项卡)

    这里可以看见已经安装的plugin以及可以安装的plugin。这里我们安装:

      Maven项目插件:Maven Integration plugin,这个插件可以让我们创建一个maven构建任务。

      ssh传输工具插件:Publish Over SSH 项目打包完成后,使用这个插件,通过ssh的方式传输到远程服务器。

      Gitlab插件:允许Jenkins访问gitlab服务器,拉取代码

    3. 简单使用

      maven + gitlab + maven 实现自动部署。

     1. 安装git

    yum install git

    测试:

    [root@localhost ~]# git --version
    git version 1.8.3.1

    2.安装maven

    http://mirrors.cnnic.cn/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz
    tar xf apache-maven-3.3.9-bin.tar.gz

    (1)设置环境变量:

    vi ~/.bashrc

    (2)重新加载

    source ~/.bashrc

    (3)查看mvn版本:

    [root@localhost bin]# mvn -v
    Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-10T11:41:47-05:00)
    Maven home: /opt/mvn/apache-maven-3.3.9
    Java version: 1.8.0_262, vendor: Oracle Corporation
    Java home: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64/jre
    Default locale: en_US, platform encoding: UTF-8
    OS name: "linux", version: "3.10.0-1127.el7.x86_64", arch: "amd64", family: "unix"

      最后最好是修改mvn的本地仓库地址和默认的中央仓库。如果可以, 将自己本地仓库的文件上传到服务器仓库。参考文末的补充信息。

    3.到jenkins中设置

    (1) 设置凭据

    (2)配置账号:Jenkins >> Manage Jenkins >> System Configuration >> Configure System 中设置:

    4. 创建项目:NewItem选择maven Project

     (1)设置git代码以及使用的凭证

     

     (2)设定maven的时候需要设定一些参数

    主要如下:JDK参数

     git如下:

     maven如下:

    5.保存之后进行构建

    (1) 点击立即构建

    (2)点击构建会在左下角显示构建的进度

    (3) 查看构建状态

     可以看到拉取的代码是最新的代码,带gitlab查看也可以看出来。

    (4)查看构建日志如下:(Console Output)

    Started by user root
    Running as SYSTEM
    Building in workspace /root/.jenkins/workspace/publisSSmTemplate
    The recommended git tool is: NONE
    using credential gitlab
     > git rev-parse --is-inside-work-tree # timeout=10
    Fetching changes from the remote Git repository
     > git config remote.origin.url http://192.168.1.129:8080/root/ssmTemplate.git # timeout=10
    Fetching upstream changes from http://192.168.1.129:8080/root/ssmTemplate.git
     > git --version # timeout=10
     > git --version # 'git version 1.8.3.1'
    using GIT_ASKPASS to set credentials 登录gitlab
     > git fetch --tags --progress http://192.168.1.129:8080/root/ssmTemplate.git +refs/heads/*:refs/remotes/origin/* # timeout=10
     > git rev-parse refs/remotes/origin/master^{commit} # timeout=10
     > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
    Checking out Revision 36fef432e1a56fec22a138eeeed933620cbeb4fe (refs/remotes/origin/master)
     > git config core.sparsecheckout # timeout=10
     > git checkout -f 36fef432e1a56fec22a138eeeed933620cbeb4fe # timeout=10
    Commit message: "xx"
     > git rev-list --no-walk 737c9a7f654bbd67e404980c2de51cca0d58a025 # timeout=10
    Parsing POMs
    Discovered a new module cn.qlq:ssmtemplate ssmtemplate
    Modules changed, recalculating dependency graph
    Established TCP socket on 43537
    [publisSSmTemplate] $ /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64/bin/java -cp /root/.jenkins/plugins/maven-plugin/WEB-INF/lib/maven33-agent-1.13.jar:/opt/mvn/apache-maven-3.3.9/boot/plexus-classworlds-2.5.2.jar:/opt/mvn/apache-maven-3.3.9/conf/logging jenkins.maven3.agent.Maven33Main /opt/mvn/apache-maven-3.3.9 /root/.jenkins/war/WEB-INF/lib/remoting-4.5.jar /root/.jenkins/plugins/maven-plugin/WEB-INF/lib/maven33-interceptor-1.13.jar /root/.jenkins/plugins/maven-plugin/WEB-INF/lib/maven3-interceptor-commons-1.13.jar 43537
    <===[JENKINS REMOTING CAPACITY]===>channel started
    Executing Maven:  -B -f /root/.jenkins/workspace/publisSSmTemplate/pom.xml clean package
    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] ------------------------------------------------------------------------
    [INFO] Building ssmtemplate 0.0.1-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    [INFO] 
    [INFO] --- maven-clean-plugin:2.6.1:clean (default-clean) @ ssmtemplate ---
    [INFO] 
    [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ ssmtemplate ---
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 1 resource
    [INFO] Copying 5 resources
    [INFO] 
    [INFO] --- maven-compiler-plugin:3.5.1:compile (default-compile) @ ssmtemplate ---
    [INFO] Changes detected - recompiling the module!
    [INFO] Compiling 4 source files to /root/.jenkins/workspace/publisSSmTemplate/target/classes
    [INFO] 
    [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ ssmtemplate ---
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 1 resource
    [INFO] 
    [INFO] --- maven-compiler-plugin:3.5.1:testCompile (default-testCompile) @ ssmtemplate ---
    [INFO] Nothing to compile - all classes are up to date
    [INFO] 
    [INFO] --- maven-surefire-plugin:2.18.1:test (default-test) @ ssmtemplate ---
    [JENKINS] Recording test results
    [INFO] 
    [INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ ssmtemplate ---
    [INFO] Building jar: /root/.jenkins/workspace/publisSSmTemplate/target/ssmtemplate-0.0.1-SNAPSHOT.jar
    [INFO] 
    [INFO] --- spring-boot-maven-plugin:1.5.2.RELEASE:repackage (default) @ ssmtemplate ---
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 01:20 min
    [INFO] Finished at: 2020-09-15T18:34:01-04:00
    [INFO] Final Memory: 28M/69M
    [INFO] ------------------------------------------------------------------------
    Waiting for Jenkins to finish collecting data
    [JENKINS] Archiving /root/.jenkins/workspace/publisSSmTemplate/pom.xml to cn.qlq/ssmtemplate/0.0.1-SNAPSHOT/ssmtemplate-0.0.1-SNAPSHOT.pom
    [JENKINS] Archiving /root/.jenkins/workspace/publisSSmTemplate/target/ssmtemplate-0.0.1-SNAPSHOT.jar to cn.qlq/ssmtemplate/0.0.1-SNAPSHOT/ssmtemplate-0.0.1-SNAPSHOT.jar
    channel stopped
    Finished: SUCCESS

    (5) 到项目的工作空间查看:

    [root@localhost publisSSmTemplate]# pwd
    /root/.jenkins/workspace/publisSSmTemplate
    [root@localhost publisSSmTemplate]# ls target/
    classes  generated-sources  maven-archiver  maven-status  ssmtemplate-0.0.1-SNAPSHOT.jar  ssmtemplate-0.0.1-SNAPSHOT.jar.original  test-classes

    可以看到打包成功,生成了指定的jar包。

    (6) 用jenkins自动打包的jar包启动后查看:

    [root@localhost publisSSmTemplate]# cd target/
    [root@localhost target]# ls
    classes  generated-sources  maven-archiver  maven-status  ssmtemplate-0.0.1-SNAPSHOT.jar  ssmtemplate-0.0.1-SNAPSHOT.jar.original  test-classes
    [root@localhost target]# java -jar ssmtemplate-0.0.1-SNAPSHOT.jar --server.port=9000

    浏览器访问:

     6. 构建后自动启动打包后的脚本,编写shell脚本启动

     (1)在/opt/jar目录下创建脚本,脚本内容如下: 用于执行管理服务

    #!/bin/bash
    #这里可替换为你自己的执行程序,其他代码无需更改
    APP_NAME=ssmtemplate-0.0.1-SNAPSHOT.jar
    
    #使用说明,用来提示输入参数
    usage() {
    echo "Usage: sh 脚本名.sh [start|stop|restart|status]"
    exit 1
    }
    
    #检查程序是否在运行
    is_exist(){
    pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' `
    #如果不存在返回1,存在返回0
    if [ -z "${pid}" ]; then
    return 1
    else
    return 0
    fi
    }
    
    #启动方法
    start(){
    is_exist
    if [ $? -eq "0" ]; then
    echo "${APP_NAME} is already running. pid=${pid} ."
    else
    nohup java -jar $APP_NAME --server.port=9000 > log.file 2>&1 &
    echo "${APP_NAME} start success"
    fi
    }
    
    #停止方法
    stop(){
    is_exist
    if [ $? -eq "0" ]; then
    kill -9 $pid
    echo "kill pid ${pid}"
    else
    echo "${APP_NAME} is not running"
    fi
    }
    
    #输出运行状态
    status(){
    is_exist
    if [ $? -eq "0" ]; then
    echo "${APP_NAME} is running. Pid is ${pid}"
    else
    echo "${APP_NAME} is NOT running."
    fi
    }
    
    #重启
    restart(){
    stop
    start
    }
    
    #根据输入参数,选择执行对应方法,不输入则执行使用说明
    case "$1" in
    "start")
    start
    ;;
    "stop")
    stop
    ;;
    "status")
    status
    ;;
    "restart")
    restart
    ;;
    *)
    usage
    ;;
    esac

    (2)启动测试

    [root@localhost jar]# sh ssm.sh start
    ssmtemplate-0.0.1-SNAPSHOT.jar start success
    [root@localhost jar]# jps -l
    91937 sun.tools.jps.Jps
    60695 jenkins.war
    91917 ssmtemplate-0.0.1-SNAPSHOT.jar

    浏览器访问:

     (3)杀掉上面进程

    [root@localhost jar]# jps -l
    92500 sun.tools.jps.Jps
    60695 jenkins.war
    [root@localhost jar]# ls
    log.file  ssm.sh

    (4)jenkins中编写脚本

    jenkins->publisSSmTemplate->configure中Post Steps编写脚本:

    内容如下:

    pwd
    cp ./target/ssmtemplate*.jar /opt/jar/
    cd /opt/jar/
    sh ssm.sh restart

    截图如下:(添加pwd是为了在日志中查看当前目录,默认的当前目录是在 jenkins安装目录/workspace/工程名称下)

     (5) 点击buildnow立即构建后查看构建输出信息,如下:

    Started by user root
    Running as SYSTEM
    Building in workspace /root/.jenkins/workspace/publisSSmTemplate
    The recommended git tool is: NONE
    using credential gitlab
     > git rev-parse --is-inside-work-tree # timeout=10
    Fetching changes from the remote Git repository
     > git config remote.origin.url http://192.168.1.129:8080/root/ssmTemplate.git # timeout=10
    Fetching upstream changes from http://192.168.1.129:8080/root/ssmTemplate.git
     > git --version # timeout=10
     > git --version # 'git version 1.8.3.1'
    using GIT_ASKPASS to set credentials 登录gitlab
     > git fetch --tags --progress http://192.168.1.129:8080/root/ssmTemplate.git +refs/heads/*:refs/remotes/origin/* # timeout=10
     > git rev-parse refs/remotes/origin/master^{commit} # timeout=10
     > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
    Checking out Revision 36fef432e1a56fec22a138eeeed933620cbeb4fe (refs/remotes/origin/master)
     > git config core.sparsecheckout # timeout=10
     > git checkout -f 36fef432e1a56fec22a138eeeed933620cbeb4fe # timeout=10
    Commit message: "xx"
     > git rev-list --no-walk 36fef432e1a56fec22a138eeeed933620cbeb4fe # timeout=10
    Parsing POMs
    Established TCP socket on 41890
    [publisSSmTemplate] $ /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64/bin/java -cp /root/.jenkins/plugins/maven-plugin/WEB-INF/lib/maven33-agent-1.13.jar:/opt/mvn/apache-maven-3.3.9/boot/plexus-classworlds-2.5.2.jar:/opt/mvn/apache-maven-3.3.9/conf/logging jenkins.maven3.agent.Maven33Main /opt/mvn/apache-maven-3.3.9 /root/.jenkins/war/WEB-INF/lib/remoting-4.5.jar /root/.jenkins/plugins/maven-plugin/WEB-INF/lib/maven33-interceptor-1.13.jar /root/.jenkins/plugins/maven-plugin/WEB-INF/lib/maven3-interceptor-commons-1.13.jar 41890
    <===[JENKINS REMOTING CAPACITY]===>channel started
    Executing Maven:  -B -f /root/.jenkins/workspace/publisSSmTemplate/pom.xml clean package
    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] ------------------------------------------------------------------------
    [INFO] Building ssmtemplate 0.0.1-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    [INFO] 
    [INFO] --- maven-clean-plugin:2.6.1:clean (default-clean) @ ssmtemplate ---
    [INFO] Deleting /root/.jenkins/workspace/publisSSmTemplate/target
    [INFO] 
    [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ ssmtemplate ---
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 1 resource
    [INFO] Copying 5 resources
    [INFO] 
    [INFO] --- maven-compiler-plugin:3.5.1:compile (default-compile) @ ssmtemplate ---
    [INFO] Changes detected - recompiling the module!
    [INFO] Compiling 4 source files to /root/.jenkins/workspace/publisSSmTemplate/target/classes
    [INFO] 
    [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ ssmtemplate ---
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 1 resource
    [INFO] 
    [INFO] --- maven-compiler-plugin:3.5.1:testCompile (default-testCompile) @ ssmtemplate ---
    [INFO] Nothing to compile - all classes are up to date
    [INFO] 
    [INFO] --- maven-surefire-plugin:2.18.1:test (default-test) @ ssmtemplate ---
    [JENKINS] Recording test results
    [INFO] 
    [INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ ssmtemplate ---
    [INFO] Building jar: /root/.jenkins/workspace/publisSSmTemplate/target/ssmtemplate-0.0.1-SNAPSHOT.jar
    [INFO] 
    [INFO] --- spring-boot-maven-plugin:1.5.2.RELEASE:repackage (default) @ ssmtemplate ---
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 01:14 min
    [INFO] Finished at: 2020-09-15T19:30:38-04:00
    [INFO] Final Memory: 28M/69M
    [INFO] ------------------------------------------------------------------------
    Waiting for Jenkins to finish collecting data
    [JENKINS] Archiving /root/.jenkins/workspace/publisSSmTemplate/pom.xml to cn.qlq/ssmtemplate/0.0.1-SNAPSHOT/ssmtemplate-0.0.1-SNAPSHOT.pom
    [JENKINS] Archiving /root/.jenkins/workspace/publisSSmTemplate/target/ssmtemplate-0.0.1-SNAPSHOT.jar to cn.qlq/ssmtemplate/0.0.1-SNAPSHOT/ssmtemplate-0.0.1-SNAPSHOT.jar
    [publisSSmTemplate] $ /bin/sh -xe /tmp/jenkins7873121033192777679.sh
    channel stopped
    + pwd
    /root/.jenkins/workspace/publisSSmTemplate
    + cp ./target/ssmtemplate-0.0.1-SNAPSHOT.jar /opt/jar/
    + cd /opt/jar/
    + sh ssm.sh restart
    ssmtemplate-0.0.1-SNAPSHOT.jar is not running
    ssmtemplate-0.0.1-SNAPSHOT.jar start success
    Finished: SUCCES

     浏览器访问测试:

    访问404. 虽然构建成功,但是服务没启动,查看java进程如下:

    [root@localhost jar]# jps -l
    60695 jenkins.war
    97678 sun.tools.jps.Jps

    原因:  Jenkins-Post Steps-Exec command 运行脚本,脚本执行结束,显示Success,但是服务未启动。Jenkins build结束后会kill掉衍生进程。

    解决办法:可以尝试重置变量BUILD_ID,避免Jenkins kill进程。 

    shell脚本中添加如下命令:

    #!/bin/sh
    export BUILD_ID=anyWordIsOk

    重新构建成功后访问:

     

       至此完成了简单的构建。可以实现自动打包以及执行shell脚本启动服务,只不过打包和启动都是在jenkins所在的服务器,下面研究打包后上传到另一个应用服务器并启动。

    7. 构建完成后上传到另一台服务器并执行远程的脚本

    这个依赖于Publish Over SSH插件,所以先安装上这个插件。

    (0) 准备另一台服务器,用于发布服务,也就是接收jenkins打包后的文件:(ip是130)

    [root@localhost ssm]# pwd
    /opt/jar/ssm
    [root@localhost ssm]# ls
    ssm.sh

    ssm.sh内容和上面的一样,用于启动服务,如下:

    #!/bin/bash
    export BUILD_ID=anyWordIsOk
    #这里可替换为你自己的执行程序,其他代码无需更改
    APP_NAME=ssmtemplate-0.0.1-SNAPSHOT.jar
    
    #使用说明,用来提示输入参数
    usage() {
    echo "Usage: sh 脚本名.sh [start|stop|restart|status]"
    exit 1
    }
    
    #检查程序是否在运行
    is_exist(){
    pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' `
    #如果不存在返回1,存在返回0
    if [ -z "${pid}" ]; then
    return 1
    else
    return 0
    fi
    }
    
    #启动方法
    start(){
    is_exist
    if [ $? -eq "0" ]; then
    echo "${APP_NAME} is already running. pid=${pid} ."
    else
    nohup java -jar $APP_NAME --server.port=9000 > log.file 2>&1 &
    echo "${APP_NAME} start success"
    fi
    }
    
    #停止方法
    stop(){
    is_exist
    if [ $? -eq "0" ]; then
    kill -9 $pid
    echo "kill pid ${pid}"
    else
    echo "${APP_NAME} is not running"
    fi
    }
    
    #输出运行状态
    status(){
    is_exist
    if [ $? -eq "0" ]; then
    echo "${APP_NAME} is running. Pid is ${pid}"
    else
    echo "${APP_NAME} is NOT running."
    fi
    }
    
    #重启
    restart(){
    stop
    start
    }
    
    #根据输入参数,选择执行对应方法,不输入则执行使用说明
    case "$1" in
    "start")
    start
    ;;
    "stop")
    stop
    ;;
    "status")
    status
    ;;
    "restart")
    restart
    ;;
    *)
    usage
    ;;
    esac

    (1) 配置SSHserver:Jenkins -》Configure System  -》 SSH Servers 点击add可以添加多个

       点击后可以测试连接。

    (2)Jenkins -》 项目名称  -》 configure 中在 Post Steps中设置上传到其他服务器:  Post Steps 可以添加多个操作。

     参数解释:

    Source files: 是上传的文件,可以用通配符的形式
    remove prefix:删除的目录,如果不填,会在目录下创建一个target目录。官方解释如下:
      if Source files were target/deployment/images/**/ then you may want Remove prefix to be target/deployment This would create the images folder under the remote directory, and not target/deployment Jenkins environment variables can be used in this path.
    Remote directory:是上传的位置(远程应用服务器位置),是一个相对路径。相对于上面SSHserver设置的路径。比如我上面设置的是/opt/jar,并且这个属性设置为/ssm。则文件上传后位于/opt/jar/ssm/ 目录
    Exec command: 是上传后执行的远程命令。在上面我也是启动服务,注意默认路径是当前登录用户的根目录。我是root用户,所以默认在/root

    (3) 重新构建并且查看日志:可以看到构建成功,并且在jenkins所在服务器和远程130服务器都启动了服务。浏览器也可以访问到。

    Started by user root
    Running as SYSTEM
    Building in workspace /root/.jenkins/workspace/publisSSmTemplate
    The recommended git tool is: NONE
    using credential gitlab
     > git rev-parse --is-inside-work-tree # timeout=10
    Fetching changes from the remote Git repository
     > git config remote.origin.url http://192.168.1.129:8080/root/ssmTemplate.git # timeout=10
    Fetching upstream changes from http://192.168.1.129:8080/root/ssmTemplate.git
     > git --version # timeout=10
     > git --version # 'git version 1.8.3.1'
    using GIT_ASKPASS to set credentials 登录gitlab
     > git fetch --tags --progress http://192.168.1.129:8080/root/ssmTemplate.git +refs/heads/*:refs/remotes/origin/* # timeout=10
     > git rev-parse refs/remotes/origin/master^{commit} # timeout=10
     > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
    Checking out Revision 36fef432e1a56fec22a138eeeed933620cbeb4fe (refs/remotes/origin/master)
     > git config core.sparsecheckout # timeout=10
     > git checkout -f 36fef432e1a56fec22a138eeeed933620cbeb4fe # timeout=10
    Commit message: "xx"
     > git rev-list --no-walk 36fef432e1a56fec22a138eeeed933620cbeb4fe # timeout=10
    Parsing POMs
    Established TCP socket on 45294
    [publisSSmTemplate] $ /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64/bin/java -cp /root/.jenkins/plugins/maven-plugin/WEB-INF/lib/maven33-agent-1.13.jar:/opt/mvn/apache-maven-3.3.9/boot/plexus-classworlds-2.5.2.jar:/opt/mvn/apache-maven-3.3.9/conf/logging jenkins.maven3.agent.Maven33Main /opt/mvn/apache-maven-3.3.9 /root/.jenkins/war/WEB-INF/lib/remoting-4.5.jar /root/.jenkins/plugins/maven-plugin/WEB-INF/lib/maven33-interceptor-1.13.jar /root/.jenkins/plugins/maven-plugin/WEB-INF/lib/maven3-interceptor-commons-1.13.jar 45294
    <===[JENKINS REMOTING CAPACITY]===>channel started
    Executing Maven:  -B -f /root/.jenkins/workspace/publisSSmTemplate/pom.xml clean package
    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] ------------------------------------------------------------------------
    [INFO] Building ssmtemplate 0.0.1-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    [INFO] 
    [INFO] --- maven-clean-plugin:2.6.1:clean (default-clean) @ ssmtemplate ---
    [INFO] Deleting /root/.jenkins/workspace/publisSSmTemplate/target
    [INFO] 
    [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ ssmtemplate ---
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 1 resource
    [INFO] Copying 5 resources
    [INFO] 
    [INFO] --- maven-compiler-plugin:3.5.1:compile (default-compile) @ ssmtemplate ---
    [INFO] Changes detected - recompiling the module!
    [INFO] Compiling 4 source files to /root/.jenkins/workspace/publisSSmTemplate/target/classes
    [INFO] 
    [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ ssmtemplate ---
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 1 resource
    [INFO] 
    [INFO] --- maven-compiler-plugin:3.5.1:testCompile (default-testCompile) @ ssmtemplate ---
    [INFO] Nothing to compile - all classes are up to date
    [INFO] 
    [INFO] --- maven-surefire-plugin:2.18.1:test (default-test) @ ssmtemplate ---
    [JENKINS] Recording test results
    [INFO] 
    [INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ ssmtemplate ---
    [INFO] Building jar: /root/.jenkins/workspace/publisSSmTemplate/target/ssmtemplate-0.0.1-SNAPSHOT.jar
    [INFO] 
    [INFO] --- spring-boot-maven-plugin:1.5.2.RELEASE:repackage (default) @ ssmtemplate ---
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 01:04 min
    [INFO] Finished at: 2020-09-15T22:18:16-04:00
    [INFO] Final Memory: 29M/69M
    [INFO] ------------------------------------------------------------------------
    Waiting for Jenkins to finish collecting data
    [JENKINS] Archiving /root/.jenkins/workspace/publisSSmTemplate/pom.xml to cn.qlq/ssmtemplate/0.0.1-SNAPSHOT/ssmtemplate-0.0.1-SNAPSHOT.pom
    [JENKINS] Archiving /root/.jenkins/workspace/publisSSmTemplate/target/ssmtemplate-0.0.1-SNAPSHOT.jar to cn.qlq/ssmtemplate/0.0.1-SNAPSHOT/ssmtemplate-0.0.1-SNAPSHOT.jar
    [publisSSmTemplate] $ /bin/sh -xe /tmp/jenkins5772284603317430255.sh
    channel stopped
    + pwd
    /root/.jenkins/workspace/publisSSmTemplate
    + cp ./target/ssmtemplate-0.0.1-SNAPSHOT.jar /opt/jar/
    + cd /opt/jar/
    + sh ssm.sh restart
    ssmtemplate-0.0.1-SNAPSHOT.jar is not running
    ssmtemplate-0.0.1-SNAPSHOT.jar start success
    SSH: Connecting from host [localhost.localdomain]
    SSH: Connecting with configuration [130server] ...
    SSH: EXEC: STDOUT/STDERR from command [cd /opt/jar/ssm
    sh ssm.sh restart] ...
    ssmtemplate-0.0.1-SNAPSHOT.jar is not running
    ssmtemplate-0.0.1-SNAPSHOT.jar start success
    SSH: EXEC: completed after 871 ms
    SSH: Disconnecting configuration [130server] ...
    SSH: Transferred 1 file(s)
    Finished: SUCCESS

    4.gitlab自动构建

      常用的应该有定时构建,cron表达式触发。token以url方式构建以及webhook自动构建。

    1. 通过生成token以url的方式构建

    (1) 到jenkins首页创建一个用户

     创建了一个 test/111222的用户。

    (2) 给用户赋权:Manage Jenkins->Configure Global Security

     (3)yongtest/111222之后登录,点击登录的用户之后点击configure -》 add API Token

     名称为:ssmtoken

     token串为:11f95b0b68a4f6a4cbb283226a249de4df

     (3) 项目设置构建选择:Trigger builds remotely (e.g., from scripts),例如: api名称是随便写的

     (4)远程访问,开始构建:

    访问如下url即可:

    http://test:11f95b0b68a4f6a4cbb283226a249de4df@192.168.1.129:8088/job/publisSSmTemplate/build?token=pubssmtoken

    规则为: http://用户名:token串@主机/job/项目名称/build?token=token名称   需要注意这里的token名称是在项目名称填写的名称,如果写错不允许构建。

    (5)查看构建日志

    Started by remote host 192.168.1.130
    Running as SYSTEM
    Building in workspace /root/.jenkins/workspace/publisSSmTemplate
    The recommended git tool is: NONE
    ...

    补充:这种方式需要每次都手动复制URL访问,可以用gitlab的webhook在代码提交后自动访问URL:

    settings -> Integrations Settings 增加webhook的url,如下:

    2.以webhook自动构建

      依赖Generic Webhook Trigger插件,先到jenkins插件管理安装该插件。

      首先我们需要先了解,gitlab在发动webhook请求的时候会携带一些请求参数以及请求头,而且不同的事件有不同的参数和头。然后我们需要在jenkins中根据不同的请求参数来判断是否进行构建。

    比如push events推送代码事件发送的数据如下:

    Request header:

    X-Gitlab-Event: Push Hook

    Request body:

    {
      "object_kind": "push",
      "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
      "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
      "ref": "refs/heads/master",
      "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
      "user_id": 4,
      "user_name": "John Smith",
      "user_username": "jsmith",
      "user_email": "john@example.com",
      "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
      "project_id": 15,
      "project":{
        "name":"Diaspora",
        "description":"",
        "web_url":"http://example.com/mike/diaspora",
        "avatar_url":null,
        "git_ssh_url":"git@example.com:mike/diaspora.git",
        "git_http_url":"http://example.com/mike/diaspora.git",
        "namespace":"Mike",
        "visibility_level":0,
        "path_with_namespace":"mike/diaspora",
        "default_branch":"master",
        "homepage":"http://example.com/mike/diaspora",
        "url":"git@example.com:mike/diaspora.git",
        "ssh_url":"git@example.com:mike/diaspora.git",
        "http_url":"http://example.com/mike/diaspora.git"
      },
      "repository":{
        "name": "Diaspora",
        "url": "git@example.com:mike/diaspora.git",
        "description": "",
        "homepage": "http://example.com/mike/diaspora",
        "git_http_url":"http://example.com/mike/diaspora.git",
        "git_ssh_url":"git@example.com:mike/diaspora.git",
        "visibility_level":0
      },
      "commits": [
        {
          "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
          "message": "Update Catalan translation to e38cb41.",
          "timestamp": "2011-12-12T14:27:31+02:00",
          "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
          "author": {
            "name": "Jordi Mallach",
            "email": "jordi@softcatala.org"
          },
          "added": ["CHANGELOG"],
          "modified": ["app/controller/application.rb"],
          "removed": []
        },
        {
          "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
          "message": "fixed readme",
          "timestamp": "2012-01-03T23:36:29+02:00",
          "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
          "author": {
            "name": "GitLab dev user",
            "email": "gitlabdev@dv6700.(none)"
          },
          "added": ["CHANGELOG"],
          "modified": ["app/controller/application.rb"],
          "removed": []
        }
      ],
      "total_commits_count": 4
    }

     (1)到项目中配置Build Triggers 选择: Generic Webhook Trigger,进行参数设置

    Post content parameters添加两个变量:从请求参数提取ref和project.name赋值到tempref和projectname变量中

     

    下面两个打上对勾,便于查看日志:

     Optional filter配置验证:验证上面提取的变量组合起来(Text中)是否满足Expression表达式(这里验证是ssmTemplate项目的master分支)。如果满足会进行下面的构建操作,不满足不会进行构建

     (2)验证:在另一台服务器上访问:

    直接访问webhookurl发现没权限:

    [root@localhost ssm]# curl http://192.168.1.129:8088//generic-webhook-trigger/invoke
    {"jobs":null,"message":"Did not find any jobs with GenericTrigger configured! If you are using a token, you need to pass it like ...trigger/invoke?token=TOKENHERE. If you are not using a token, you need to authenticate like http://user:passsword@jenkins/generic-webhook... "}

     可以加用户名密码进行访问,如下: 说明已经访问到webhook的url,只是参数不匹配

    [root@localhost ssm]# curl http://test:111222@192.168.1.129:8088//generic-webhook-trigger/invoke
    {"jobs":{"publisSSmTemplate":{"regexpFilterExpression":"^(ssmTemplate-refs/heads/master)$","triggered":false,"resolvedVariables":{"projectname":"","tempref":""},"regexpFilterText":"-","id":0,"url":""}},"message":"Triggered jobs."}

    (3)到gitlab配置webhook为上面地址:

    (4)向master分支提交代码查看构建记录:可以看到是通过webhook产生的构建,并且打印了post content和variables

     (5)测试向其他分支提交代码发现不会触发构建。

    基于webhook的自动构建也完成。

    补充:构建方式还有基于cron表达式周期性执行以及基于其他项目构建完成之后开始构建等方法,如下:

    补充: jenkins的版本是2.249.1

      这个可以到 jenkins的安装目录下config.xml中查看。

     补充: jenkins配置账户

    Manage Jenkins -》 Manager Users 进行设置,可以创建用户。

    补充:jenkins的工作空间默认在安装目录下的workspace目录

    比如我新建了一个任务是 publisSSmTemplate, 在jenkins目录下会有一个对应的目录,拉下来的代码会放在该目录,比如:

    [root@localhost publisSSmTemplate]# pwd
    /root/.jenkins/workspace/publisSSmTemplate

    补充:mvn仓库的默认位置是 ${user.home}/.m2/repository 目录,中央仓库默认是http://repo1.maven.org/maven2/,可以通过修改 mvn/conf/settings.xml 中修改。 

    (1)查看默认仓库:

    [root@localhost conf]# head -n 50 settings.xml 
    <?xml version="1.0" encoding="UTF-8"?>
    
    <!--
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at
    
        http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
    -->
    
    <!--
     | This is the configuration file for Maven. It can be specified at two levels:
     |
     |  1. User Level. This settings.xml file provides configuration for a single user,
     |                 and is normally provided in ${user.home}/.m2/settings.xml.
     |
     |                 NOTE: This location can be overridden with the CLI option:
     |
     |                 -s /path/to/user/settings.xml
     |
     |  2. Global Level. This settings.xml file provides configuration for all Maven
     |                 users on a machine (assuming they're all using the same Maven
     |                 installation). It's normally provided in
     |                 ${maven.home}/conf/settings.xml.
     |
     |                 NOTE: This location can be overridden with the CLI option:
     |
     |                 -gs /path/to/global/settings.xml
     |
     | The sections in this sample file are intended to give you a running start at
     | getting the most out of your Maven installation. Where appropriate, the default
     | values (values used when the setting is not specified) are provided.
     |
     |-->
    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
      <!-- localRepository
       | The path to the local repository maven will use to store artifacts.
    [root@localhost conf]# head -n 60 settings.xml 
    <?xml version="1.0" encoding="UTF-8"?>
    
    <!--
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at
    
        http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
    -->
    
    <!--
     | This is the configuration file for Maven. It can be specified at two levels:
     |
     |  1. User Level. This settings.xml file provides configuration for a single user,
     |                 and is normally provided in ${user.home}/.m2/settings.xml.
     |
     |                 NOTE: This location can be overridden with the CLI option:
     |
     |                 -s /path/to/user/settings.xml
     |
     |  2. Global Level. This settings.xml file provides configuration for all Maven
     |                 users on a machine (assuming they're all using the same Maven
     |                 installation). It's normally provided in
     |                 ${maven.home}/conf/settings.xml.
     |
     |                 NOTE: This location can be overridden with the CLI option:
     |
     |                 -gs /path/to/global/settings.xml
     |
     | The sections in this sample file are intended to give you a running start at
     | getting the most out of your Maven installation. Where appropriate, the default
     | values (values used when the setting is not specified) are provided.
     |
     |-->
    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
      <!-- localRepository
       | The path to the local repository maven will use to store artifacts.
       |
       | Default: ${user.home}/.m2/repository
      <localRepository>/path/to/local/repo</localRepository>
      -->

    (2) 修改默认仓库以及默认中央仓库

    <localRepository>/opt/mvn/repository</localRepository>

    仓库:

      <mirrors>
        <mirror>
          <id>alimaven</id>
          <name>aliyun maven</name>  
          <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
          <mirrorOf>central</mirrorOf>
        </mirror>
     </mirrors>

    (3) 可以将自己本地仓库的文件上传到服务器,可以减少第一次打包下载依赖的时间。

    补充:上面的脚本获取jar包名称也可以采用脚本的方式获取,例如:

    #!/bin/bash
    export BUILD_ID=anyWordIsOk
    
    # 获取当前文件夹下面的jar名称
    APP_NAME=tmp
    basepath=$(cd `dirname $0`; pwd)
    dir=$(ls -l $basepath )
    for i in $dir
    do
        if [ "${i##*.}" == "jar" ]
        then
            APP_NAME=$i
        fi
    done
    
    #使用说明,用来提示输入参数
    usage() {
    echo "Usage: sh 脚本名.sh [start|stop|restart|status]"
    exit 1
    }
    
    #检查程序是否在运行
    is_exist(){
    pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' `
    #如果不存在返回1,存在返回0
    if [ -z "${pid}" ]; then
    return 1
    else
    return 0
    fi
    }
    
    #启动方法
    start(){
    is_exist
    if [ $? -eq "0" ]; then
    echo "${APP_NAME} is already running. pid=${pid} ."
    else
    nohup java -jar $APP_NAME --server.port=9000 > log.file 2>&1 &
    echo "${APP_NAME} start success"
    fi
    }
    
    #停止方法
    stop(){
    is_exist
    if [ $? -eq "0" ]; then
    kill -9 $pid
    echo "kill pid ${pid}"
    else
    echo "${APP_NAME} is not running"
    fi
    }
    
    #输出运行状态
    status(){
    is_exist
    if [ $? -eq "0" ]; then
    echo "${APP_NAME} is running. Pid is ${pid}"
    else
    echo "${APP_NAME} is NOT running."
    fi
    }
    
    #重启
    restart(){
    stop
    start
    }
    
    #根据输入参数,选择执行对应方法,不输入则执行使用说明
    case "$1" in
    "start")
    start
    ;;
    "stop")
    stop
    ;;
    "status")
    status
    ;;
    "restart")
    restart
    ;;
    *)
    usage
    ;;
    esac
  • 相关阅读:
    整数划分递归模板
    最近点对算法模板
    计算几何模板
    poj1269---直线位置关系
    poj1017----模拟
    MVC 提交List 集合 注意对应的参数名称
    使用 WebClient 來存取 GET,POST,PUT,DELETE,PATCH 網路資源
    对路径访问拒绝,要加上具体filename/c.png
    sql 列集合
    百度地图 Infowidow 内容(content 下标签) 点击事件
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/13669797.html
Copyright © 2011-2022 走看看