admin 管理员组

文章数量: 1086019

纯java的方式实现自定义自动化部署java项目

纯java的方式实现自定义自动化部署java项目

  • 前言
  • 使用第三方的服务或插件实现部署所存在的问题
  • 自动化部署java项目
      • java项目部署方式
        • 流程
      • 代码实现
        • 打包
        • 使用 java 执行 cmd 进行打包
        • 上传jar包到服务器指定路径
        • 远程执行Linux命令启动项目
        • main方法与完整代码
  • 总结

前言

关于自动化部署java项目的方案有很多,就比如说比较知名的使用 Jenkins 实现自动化部署,还有比如说使用 IDEA 中的插件 Alibaba Cloud Toolkit 实现自动化部署;其他的方式我也没太去了解,我现在要做的是使用java自定义部署项目

使用第三方的服务或插件实现部署所存在的问题

关于 Jenkins 我学习也使用了一会, Alibaba Cloud Toolkit 也试着用了一会,个人感觉用着有以下几点问题

1.Jenkins 安装及配置方面有点繁琐(对于小型项目来说没必要,对于大型的分布式项目来说会比较有用)
2.Jenkins 需要安装到服务器上,使用的时候会占用大量的服务器资源 (除非你专门给Jenkins配一个服务器或者装自己电脑上)
3.上手需要一定的时间,不同人熟悉需要的时间不同
4.因为这些东西并不是自己做的,没法完全按照自己的想法进行部署,遇到一些疑问解决起来比较麻烦耗时间

正因如此我就想能不能按照自己的项目部署方式来定制一套自动化部署的功能


自动化部署java项目


java项目部署方式

我的项目使用的是 spring boot,打包后也就只有2个jar包需要部署到服务器, 业务量决定了是否需要分布式

流程

1. maven 项目执行 clean 再 package 或者点击 IDEA 的 clean 再 package
2. 在 target 目录下找到需要部署的jar包上传至服务器 (同时备份这次上传的jar包)
3. 启动项目

代码实现

我直接将执行代码放在项目的 test 中执行

打包

mvn package 执行的时候默认会去调用 test 中的测试方法,为避免影响打包结果我选择了修改pom,当然了,你也可以选择修改打包命令
解决方法参考:

pom 中加入 或 修改以下代码
<plugin>  <groupId>org.apache.maven.plugin</groupId>  <artifactId>maven-compiler-plugin</artifactId>  <version>3.1</version>  <configuration>  <skip>true</skip>  </configuration>  
</plugin>  
<plugin>  <groupId>org.apache.maven.plugins</groupId>  <artifactId>maven-surefire-plugin</artifactId>  <version>2.5</version>  <configuration>  <skip>true</skip>  </configuration>  
</plugin>
或者修改打包命令为
mvn package -Dmaven.test.skip=true 

使用 java 执行 cmd 进行打包

java执行cmd参考:

执行 cmd 命令时内容前需要加 cmd /c
例如原 cmd 命令:mvn package
修改后 cmd 命令:cmd /c mvn package

/*** 执行cmd命令* @param cmd cmd命令*/public static void cmd(String cmd) {Runtime run = Runtime.getRuntime();try {Process p = run.exec(cmd);InputStream ins = p.getInputStream();new Thread(() -> {String line = null;byte[] b = new byte[1024];int num = 0;try {while ((num = ins.read(b)) != -1) {System.out.print(byteToStr(b));}} catch (Exception e) {e.printStackTrace();}}).start();p.waitFor();} catch (Exception e) {e.printStackTrace();}}public static String byteToStr(byte[] buffer) {try {int length = 0;for (int i = 0; i < buffer.length; ++i) {if (buffer[i] == 0) {length = i;break;}}return new String(buffer, 0, length, "gb2312");} catch (Exception e) {return "";}}

上传jar包到服务器指定路径

java实现上传文件到服务器参考自:.html

先在pom中加入相关依赖
<dependency><groupId>com.jcraft</groupId><artifactId>jsch</artifactId><version>0.1.51</version>
</dependency>
java实现使用ssh上传文件到Linux服务器

这里我就直接以用户名密码的方式进行连接,如果要通过其他方式登录那就需要自己再稍微改一下了

/*** 上传文件到服务器* @param host       主机地址* @param port       ssh端口* @param username   用户名* @param password   密码* @param localFile  本地文件* @param serverPath 上传到服务器的路径* @throws Exception*/public static void sshSftp(String host, int port, String username, String password, String localFile, String serverPath)throws Exception {Session session = null;ChannelSftp channel = null;JSch jsch = new JSch();if (port <= 0) {//连接服务器,采用默认端口session = jsch.getSession(username, host);} else {//采用指定的端口连接服务器session = jsch.getSession(username, host, port);}//如果服务器连接不上,则抛出异常if (session == null) {throw new Exception("session is null");}//设置第一次登陆的时候提示,可选值:(ask | yes | no)session.setConfig("StrictHostKeyChecking", "no");// 如果出现了 com.jcraft.jsch.JSchException: Algorithm negotiation fail ,就加入下面这条// session.setConfig("kex", "diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group-exchange-sha256");//设置登陆主机的密码session.setPassword(password);//设置密码//设置登陆超时时间session.connect();try {//创建sftp通信通道channel = (ChannelSftp) session.openChannel("sftp");channel.connect(1000);ChannelSftp sftp = (ChannelSftp) channel;//进入服务器指定的文件夹sftp.cd(serverPath);//列出服务器指定的文件列表for (Object o : sftp.ls("*")) {System.out.println(o);}//以下代码实现从本地上传一个文件到服务器,如果要实现下载,对换以下流就可以了File file = new File(localFile);OutputStream outstream = sftp.put(file.getName());InputStream instream = new FileInputStream(file);byte[] b = new byte[1024];int n;while ((n = instream.read(b)) != -1) {outstream.write(b, 0, n);}outstream.flush();outstream.close();instream.close();} catch (Exception e) {e.printStackTrace();} finally {session.disconnect();if (channel != null) {channel.disconnect();}}}

远程执行Linux命令启动项目

java实现远程执行Linux参考自:

这里我就直接以用户名密码的方式进行连接,如果要通过其他方式登录那就需要自己再稍微改一下了
默认执行单条命令,如要执行多条命令可在每条命令结尾加 \n 表示换行执行下条命令,
例如:“pwd \n” + “cd /app/server \n” + “pwd”

    /*** 远程执行Linux命令* @param host     主机地址* @param port     端口* @param username 用户名* @param password 密码* @param sh       执行内容*/public static void shell(String host, int port, String username, String password, String sh) {JSch jSch = new JSch();Session session = null;ChannelExec channelExec = null;BufferedReader inputStreamReader = null;BufferedReader errInputStreamReader = null;try {// 1. 获取 ssh sessionsession = jSch.getSession(username, host, port);session.setPassword(password);session.setTimeout(3000);session.setConfig("StrictHostKeyChecking", "no");// 如果出现了 com.jcraft.jsch.JSchException: Algorithm negotiation fail ,就加入下面这条//session.setConfig("kex", "diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group-exchange-sha256");session.connect();  // 获取到 ssh session// 2. 通过 exec 方式执行 shell 命令channelExec = (ChannelExec) session.openChannel("exec");channelExec.setCommand(sh);channelExec.connect();  // 执行命令// 3. 获取标准输入流inputStreamReader = new BufferedReader(new InputStreamReader(channelExec.getInputStream()));// 4. 获取标准错误输入流errInputStreamReader = new BufferedReader(new InputStreamReader(channelExec.getErrStream()));// 5. 记录命令执行 logString line = null;while ((line = inputStreamReader.readLine()) != null) {System.out.println(line);}// 6. 记录命令执行错误 logString errLine = null;while ((errLine = errInputStreamReader.readLine()) != null) {System.err.println(errLine);}} catch (Exception e) {e.printStackTrace();} finally {try {if (inputStreamReader != null) {inputStreamReader.close();}if (errInputStreamReader != null) {errInputStreamReader.close();}if (channelExec != null) {channelExec.disconnect();}if (session != null) {session.disconnect();}} catch (IOException e) {e.printStackTrace();}}}

main方法与完整代码

首先执行 cmd 命令 mvn clean 与 mvn package,再设置本地的jar包路径上传jar包,执行相关命令,例如我每次部署都习惯先备份一遍今天的jar包,最后执行事先准备好的shell脚本启动项目

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;/*** @Author: asus* @Date: 1919/8/10 11:45:14*/
public class ReleasePackage {public static void main(String[] args) {long start = System.currentTimeMillis();// 打包long s = System.currentTimeMillis();System.out.println("==================== 清理 ====================");cmd("cmd /c mvn clean");System.out.println("清理耗时:" + (System.currentTimeMillis() - s) + "ms");s = System.currentTimeMillis();System.out.println("==================== 打包 ====================");cmd("cmd /c mvn package");System.out.println("打包耗时:" + (System.currentTimeMillis() - s) + "ms");// 服务器地址String host = "11.4.5.14";// ssh端口默认22int port = 24;// 用户名String username = "1919";// 密码String password = "1145141919810";// 上传文件到服务器try {s = System.currentTimeMillis();String localFile = "C:\\ho\\admin\\target\\admin.jar";String serverPath = "/app/ho";System.out.println("上传 " + localFile + " 到 " + serverPath);sshSftp(host, port, username, password, localFile, serverPath);System.out.println(localFile + "=== 上传完成 ===");System.out.println("上传文件1耗时:" + (System.currentTimeMillis() - s) + "ms");s = System.currentTimeMillis();localFile = "C:\\ho\\user\\target\\user.jar";System.out.println("上传 " + localFile + " 到 " + serverPath);sshSftp(host, port, username, password, localFile, serverPath);System.out.println(localFile + "=== 上传完成 ===");System.out.println("上传文件2耗时:" + (System.currentTimeMillis() - s) + "ms");// 备份jar包并启动项目s = System.currentTimeMillis();System.out.println("===== 开始启动 =====");String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());String sh = "cd /app/ho \n" +"mkdir /app/ho/backup/" + date + "\n" +"\\cp admin.jar backup/" + date + "/\n" +"\\cp user.jar backup/" + date + "/\n" +"/app/ho/run.sh \n";shell(host, port, username, password, sh);System.out.println("===== 操作完成 =====");System.out.println("命令执行耗时:" + (System.currentTimeMillis() - s) + "ms");} catch (Exception e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("操作总耗时:" + ((end - start) / 1000) + "s" + ((end - start) % 1000) + "ms");}/*** 执行cmd命令* 参考自 ** @param cmd cmd命令*/public static void cmd(String cmd) {Runtime run = Runtime.getRuntime();try {Process p = run.exec(cmd);InputStream ins = p.getInputStream();new Thread(() -> {String line = null;byte[] b = new byte[1024];int num = 0;try {while ((num = ins.read(b)) != -1) {System.out.print(byteToStr(b));}} catch (Exception e) {e.printStackTrace();}}).start();p.waitFor();} catch (Exception e) {e.printStackTrace();}}/*** 远程执行Linux命令* 参考自 ** @param host     主机地址* @param port     端口* @param username 用户名* @param password 密码* @param sh       执行内容*/public static void shell(String host, int port, String username, String password, String sh) {JSch jSch = new JSch();Session session = null;ChannelExec channelExec = null;BufferedReader inputStreamReader = null;BufferedReader errInputStreamReader = null;try {// 1. 获取 ssh sessionsession = jSch.getSession(username, host, port);session.setPassword(password);session.setTimeout(3000);session.setConfig("StrictHostKeyChecking", "no");// 如果出现了 com.jcraft.jsch.JSchException: Algorithm negotiation fail ,就加入下面这条// session.setConfig("kex", "diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group-exchange-sha256");session.connect();  // 获取到 ssh session// 2. 通过 exec 方式执行 shell 命令channelExec = (ChannelExec) session.openChannel("exec");channelExec.setCommand(sh);channelExec.connect();  // 执行命令// 3. 获取标准输入流inputStreamReader = new BufferedReader(new InputStreamReader(channelExec.getInputStream()));// 4. 获取标准错误输入流errInputStreamReader = new BufferedReader(new InputStreamReader(channelExec.getErrStream()));// 5. 记录命令执行 logString line = null;while ((line = inputStreamReader.readLine()) != null) {System.out.println(line);}// 6. 记录命令执行错误 logString errLine = null;while ((errLine = errInputStreamReader.readLine()) != null) {System.err.println(errLine);}} catch (Exception e) {e.printStackTrace();} finally {try {if (inputStreamReader != null) {inputStreamReader.close();}if (errInputStreamReader != null) {errInputStreamReader.close();}if (channelExec != null) {channelExec.disconnect();}if (session != null) {session.disconnect();}} catch (IOException e) {e.printStackTrace();}}}/*** 上传文件到服务器* 参考自 .html** @param host       主机地址* @param port       ssh端口* @param username   用户名* @param password   密码* @param localFile  本地文件* @param serverPath 上传到服务器的路径* @throws Exception*/public static void sshSftp(String host, int port, String username, String password, String localFile, String serverPath)throws Exception {Session session = null;ChannelSftp channel = null;JSch jsch = new JSch();if (port <= 0) {//连接服务器,采用默认端口session = jsch.getSession(username, host);} else {//采用指定的端口连接服务器session = jsch.getSession(username, host, port);}//如果服务器连接不上,则抛出异常if (session == null) {throw new Exception("session is null");}//设置第一次登陆的时候提示,可选值:(ask | yes | no)session.setConfig("StrictHostKeyChecking", "no");// 如果出现了 com.jcraft.jsch.JSchException: Algorithm negotiation fail ,就加入下面这条// session.setConfig("kex", "diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group-exchange-sha256");//设置登陆主机的密码session.setPassword(password);//设置密码//设置登陆超时时间session.connect();try {//创建sftp通信通道channel = (ChannelSftp) session.openChannel("sftp");channel.connect(1000);ChannelSftp sftp = (ChannelSftp) channel;//进入服务器指定的文件夹sftp.cd(serverPath);//列出服务器指定的文件列表for (Object o : sftp.ls("*")) {System.out.println(o);}//以下代码实现从本地上传一个文件到服务器,如果要实现下载,对换以下流就可以了File file = new File(localFile);OutputStream outstream = sftp.put(file.getName());InputStream instream = new FileInputStream(file);byte[] b = new byte[1024];int n;totalLength = file.length();finishLength = 0;startProcess();while ((n = instream.read(b)) != -1) {outstream.write(b, 0, n);finishLength += 1024;}pd.join();outstream.flush();outstream.close();instream.close();} catch (Exception e) {e.printStackTrace();} finally {session.disconnect();if (channel != null) {channel.disconnect();}}}private static String byteToStr(byte[] buffer) {try {int length = 0;for (int i = 0; i < buffer.length; ++i) {if (buffer[i] == 0) {length = i;break;}}return new String(buffer, 0, length, "gb2312");} catch (Exception e) {return "";}}private static Thread pd = new Thread();private static long totalLength = 0L;private static long finishLength = 0L;private static void startProcess() {pd = new Thread(() -> {while (finishLength < totalLength) {System.out.print(getProcess(totalLength, finishLength));try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(getProcess(totalLength, finishLength));});pd.start();}private static String getProcess(long total, long finish) {StringBuilder str = new StringBuilder();str.append("\r上传中[");int finishP = finish < total ? (int) (finish * 100 / total) : 100;for (int i = 0; i < 100; i++) {if (i > finishP) {str.append("-");} else if (i == finishP) {str.append(">");} else {str.append("=");}}str.append("]").append(finishP).append("%");return str.toString();}}



总结

这样就可以通过使用 java 的一个 main 方法进行自动化部署,实现了打包在本地部署自动化的想法,不需要熟悉额外的部署服务,本方法非常适合小型项目的部署,而且可以根据自己的需求在这基础上进行定制

本文标签: 纯java的方式实现自定义自动化部署java项目