【2020.12.06】SpringBoot + webSocket + Vue+ jcraft 实现远程执行shell命令,并把执行结果通过webSocket的方式展示在vue前端

依赖与配置

  • 依赖
 		<dependency>
            <groupId>com.jcraft</groupId>
            <artifactId>jsch</artifactId>
            <version>0.1.55</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
  • 配置注册,开启 websocket
 @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

websocket 服务

package com.ybs.websocket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName: WebSocketServer
 * @Author Paulson
 * @Date 2020/12/5
 * @Description:
 */

@Slf4j
@ServerEndpoint("/socketserver/{taskId}")
@Component
public class WebSocketServer {
    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 接收taskId
     */
    private String taskId = "";

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("taskId") String taskId) {
        this.session = session;
        this.taskId = taskId;
        if (webSocketMap.containsKey(taskId)) {
            webSocketMap.remove(taskId);
            webSocketMap.put(taskId, this);
        } else {
            webSocketMap.put(taskId, this);
            log.info(webSocketMap.toString());
        }
        try {
            sendMessage("连接成功: " + taskId);
            log.info("建立连接:{}", taskId);
        } catch (IOException e) {
            log.error("socket>>" + taskId + ",网络异常!!!!!!");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketMap.remove(taskId);
        log.info("断开连接:{}", taskId);
    }

    /**
     * 收到客户端消息后调用的方法
     * TODO 客户端交互使用,暂无用到
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        log.info("socket>>>:" + taskId + ",报文:" + message);
    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("用户错误:" + this.taskId + ",原因:" + error.getMessage());
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        //加锁,否则会出现java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method异常,并发使用session发送消息导致的
        synchronized (this.session) {
            this.session.getBasicRemote().sendText(message);
        }
    }


    public ConcurrentHashMap<String, WebSocketServer> getWebSocketMap() {
        return webSocketMap;
    }
}

连接远程服务工具类

package com.ybs.utils;

import com.jcraft.jsch.*;
import com.ybs.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Properties;

/**
 * @author weiyuanbao
 * 远程执行 shell
 * @email weiyuanbao@didiglobal.com
 * @date 2020/5/22 下午1:17
 */

@Component
@Slf4j
public class ExecuteShellUtil {

    public WebSocketServer webSocketServer;
    private static final int TIME_OUT = 10 * 60 * 1000; //设置超时为10分钟

    /**
     * 未调用初始化方法 错误提示信息
     */
    private static final String DONOT_INIT_ERROR_MSG = "please invoke init(...) first!";

    private Session session;

    private Channel channel;

    private ChannelExec channelExec;

    private ExecuteShellUtil() {
    }

    /**
     * 获取ExecuteShellUtil类实例对象
     *
     * @return 实例
     * @date 2019/4/29 16:58
     */
    public static ExecuteShellUtil getInstance() {
        return new ExecuteShellUtil();
    }

    public static ExecuteShellUtil getInstance(WebSocketServer webSocketServer) {
        ExecuteShellUtil executeShellUtil = new ExecuteShellUtil();
        executeShellUtil.webSocketServer = webSocketServer;
        return executeShellUtil;
    }

    /**
     * 初始化
     *
     * @param ip       远程Linux地址
     * @param port     端口
     * @param username 用户名
     * @param password 密码
     * @throws JSchException JSch异常
     * @date 2019/3/15 12:41
     */
    public void init(String ip, Integer port, String username, String password) throws JSchException {
        JSch jsch = new JSch();
        jsch.getSession(username, ip, port);
        session = jsch.getSession(username, ip, port);
        session.setPassword(password);
        Properties sshConfig = new Properties();
        sshConfig.put("StrictHostKeyChecking", "no");
        session.setConfig(sshConfig);
        session.connect(60 * 1000);
        session.setTimeout(TIME_OUT);
        log.info("Session connected!");
        // 打开执行shell指令的通道
        channel = session.openChannel("exec");
        channelExec = (ChannelExec) channel;
    }

    /**
     * 执行一条命令
     */
    public String execCmd(String command) throws Exception {
        if (session == null || channel == null || channelExec == null) {
            throw new Exception(DONOT_INIT_ERROR_MSG);
        }
        log.info("execCmd command - > {}", command);
        channelExec.setCommand(command);
        channel.setInputStream(null);
        channelExec.setErrStream(System.err);
        channel.connect();
        StringBuilder sb = new StringBuilder(16);
        try (InputStream in = channelExec.getInputStream();
             InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
             BufferedReader reader = new BufferedReader(isr)) {
            String buffer;
            while ((buffer = reader.readLine()) != null) {
                System.out.println(buffer);
                sb.append("\n").append(buffer);
            }
            // 释放资源
            close();
            log.info("execCmd result - > {}", sb);
            return sb.toString();
        }
    }

    /**
     * 执行一条命令
     */
    public void execCmdAndSendWebSocket(String taskId, String command) throws Exception {
        if (session == null || channel == null || channelExec == null) {
            throw new Exception(DONOT_INIT_ERROR_MSG);
        }
        log.info("execCmd command - > {}", command);
        channelExec.setCommand(command);
        channel.setInputStream(null);
        channelExec.setErrStream(System.err);
        channel.connect();

        try (InputStream in = channelExec.getInputStream();
             InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
             BufferedReader reader = new BufferedReader(isr)) {
            String buffer;
            while ((buffer = reader.readLine()) != null) {
                // 发送websocket信息
                sendMessage(taskId, buffer);
            }
            // 释放资源
            close();
        }
    }


    /**
     * 释放资源
     *
     * @date 2019/3/15 12:47
     */
    private void close() {
        if (channelExec != null && channelExec.isConnected()) {
            channelExec.disconnect();
        }
        if (channel != null && channel.isConnected()) {
            channel.disconnect();
        }
        if (session != null && session.isConnected()) {
            session.disconnect();
        }
    }


    /**
     * 连接远程机器执行 shell
     *
     * @param ip
     * @param username
     * @param password
     * @param cmd
     * @return
     * @throws Exception
     */
    public static String executeShell(String ip, String username, String password, String cmd) throws Exception {
        ExecuteShellUtil instance = ExecuteShellUtil.getInstance();
        instance.init(ip, 22, username, password);
        return instance.execCmd(cmd);
    }

    public static void executeShellAndSendWebSocket(String taskId, WebSocketServer webSocketServer, String ip, String username, String password, String cmd) throws Exception {
        ExecuteShellUtil instance = ExecuteShellUtil.getInstance(webSocketServer);
        instance.init(ip, 22, username, password);
        instance.execCmdAndSendWebSocket(taskId, cmd);
    }

    public void sendMessage(String taskId, String msg) {
        try {
            if (webSocketServer != null) {
                webSocketServer.sendMessage(msg);
            } else {
                log.warn("客户端已退出");
            }
        } catch (IOException e) {
            log.error("向客户端发送消息时出现异常,异常原因:{}", e.getMessage(), e);
        }
    }


    public static void main(String[] args) throws Exception {
        ExecuteShellUtil instance = ExecuteShellUtil.getInstance();
        instance.init("192.168.2.156", 22, "root", "mima");
        String result = instance.execCmd("cd /home/paulson/ybs; ls");
        instance.close();
        System.out.println(result);
    }

}


Service

package com.ybs.service;

import com.ybs.utils.ExecuteShellUtil;
import com.ybs.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName: DevOpsService
 * @Author Paulson
 * @Date 2020/12/5
 * @Description:
 */

@Service
@Slf4j
public class DevOpsService {

    @Autowired
    private WebSocketServer webSocketServer;

    @Async
    public void executeShellAndSendWebSocket(String taskId) throws Exception {
        ConcurrentHashMap<String, WebSocketServer> webSocketMap = webSocketServer.getWebSocketMap();
        WebSocketServer webSocketServer = webSocketMap.get(taskId);
        log.info("webSocketServer is {}, taskId is {}", webSocketServer, taskId);
        if (webSocketServer == null) {
            throw new Exception("请建立长链接!");
        }
        String cmd = "cd /home/paulson/ybs; " +
                "tail -fn300 marklog.log";
        ExecuteShellUtil.executeShellAndSendWebSocket(taskId, webSocketServer, "192.168.2.156", "root", "mima", cmd);
    }

    public void sendMessage(String taskId, String msg) {
        try {
            ConcurrentHashMap<String, WebSocketServer> map = webSocketServer.getWebSocketMap();
            WebSocketServer server = map.get(taskId);
            if (server != null) {
                server.sendMessage(msg);
            } else {
                log.warn("客户端已退出");
            }
        } catch (IOException e) {
            log.error("向客户端发送消息时出现异常,异常原因:{}", e.getMessage(), e);
        }
    }
}

@GetMapping("/devops")
    public String devops(@RequestParam String taskId) throws Exception {
        devOpsService.executeShellAndSendWebSocket(taskId);
        return "操作成功";
    }

前端

<template>
  <div>
    <h1>
      DevOps
    </h1>
    <el-button type="primary"
               @click="excute"
               style="color: red">部署日志</el-button>
    <el-button type="primary"
               @click="excute"
               style="color: red">服务日志</el-button>
    <el-button type="primary"
               @click="initWebSocket"
               style="color: red">更新</el-button>
    <span type="primary"
          style="color: yellow; margin-left:200px">执行结果:{{excute_result}}</span>
    <div style="height: 500px; overflow-y: scroll; background: #000000; color: #aaa; padding: 20px; text-align:left;margin:50px">
      <div v-html="res"
           id="logContent"></div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'test',
  data () {
    return {
      excute_result: '等待中', // 启动中   成功    失败
      websock: null,
      serviceName: 'mercury',
      res: "<div style='color: #18d035;font-size: 14px'>连接成功,等待执行....</div>"
    }
  },
  created () {
    this.initWebSocket();
  },
  destroyed () {
    this.websock.close() //离开路由之后断开websocket连接
  },
  methods: {
    excute () {
      console.log("devops")
      this.excute_result = '启动中'
      this.$axios.get('/devops', {
        params: {
          'taskId': this.serviceName
        }
      }).then(res => {
        console.log(res.data)
      })
    },
    initWebSocket () { //初始化weosocket
      const wsuri = "ws://127.0.0.1:8888/socketserver/" + this.serviceName;
      this.websock = new WebSocket(wsuri);
      this.websock.onmessage = this.websocketonmessage;
      this.websock.onopen = this.websocketonopen;
      this.websock.onerror = this.websocketonerror;
      this.websock.onclose = this.websocketclose;
    },
    websocketonopen () { //连接建立之后执行send方法发送数据
      let actions = { "test": "12345" };
      this.websocketsend(JSON.stringify(actions));
    },
    websocketonerror () {//连接建立失败重连
      this.initWebSocket();
    },
    websocketonmessage (e) { //数据接收
      console.log('收到websoct数据:', e)
      if (e.data.indexOf("Shutting down ExecutorService 'applicationTaskExecutor") != -1) {
        this.excute_result = '启动失败'
      }

      if (e.data.indexOf("ERROR") != -1 || (e.data.indexOf("Shutting")) != -1 || (e.data.indexOf("Caused by")) != -1 || (e.data.indexOf(" was already in use")) != -1) {
        this.res += "<div style='color: red;font-size: 14px'>" + e.data + "</div>"
      } else if (e.data.indexOf("Tomcat started on port(s)") != -1) {
        this.excute_result = '启动成功'
        this.res += "<div style='color: blue;font-size: 14px'>" + e.data + "</div>"
      } else {
        this.res += "<div style='color: #18d035;font-size: 14px'>" + e.data + "</div>"
      }


    },
    websocketsend (Data) {//数据发送
      this.websock.send(Data);
    },
    websocketclose (e) {  //关闭
      console.log('断开连接', e);
    },

  },
}
</script>
<style  scoped>
.el-button--primary {
  color: white;
  background-color: black;
  border-color: #1d1d1f;
}
</style>

结果展示

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值