SpringBoot 集成Shell命令实现系统日志查询并通过API返回给前端

1.目的

在做物联网项目中,为了方便产品测试人员以及第三方产品集成商能通过系统快速获取设备原始数据,因此考虑在物联网终端管理平台增加读取物联网通讯服务记录的终端日志的功能。

我的想法是直接通过Java操作Linux服务器的Shell命令,读取日志文件里面的上下行数据。

2.集成Shell命令

2.1.引入Jar包

        
            org.apache.sshd
            sshd-core
            2.8.0
        

2.2.封装Shell方法操作类

2.2.1.核心方法

/**
 * SSH linux操作类
 * @author lenny
 * @date 20230130
 */
public class SSHLinuxUtils {

    protected SSHLinuxUtils() {
    }

    /**
     * 执行Shell命令并返回结果
     * @param conn
     * @param cmd
     * @param timeout
     * @return
     * @throws IOException
     */
    public static SshResponse runCommand(SshConnection conn, String cmd, long timeout)
            throws IOException {
        SshClient client = SshClient.setUpDefaultClient();
        try {
            //Open the client
            client.start();
            //Connect to the server
            ConnectFuture cf = client.connect(conn.getUsername(), conn.getHostname(), 22);
            ClientSession session = cf.verify().getSession();
            session.addPasswordIdentity(conn.getPassword());
            session.auth().verify(TimeUnit.SECONDS.toMillis(timeout));
            //Create the exec and channel its output/error streams
            ChannelExec ce = session.createExecChannel(cmd);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ByteArrayOutputStream err = new ByteArrayOutputStream();
            ce.setOut(out);
            ce.setErr(err);
            //Execute and wait
            ce.open();
            Set events =
                    ce.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), TimeUnit.SECONDS.toMillis(timeout));
            session.close(false);
            //Check if timed out
            if (events.contains(ClientChannelEvent.TIMEOUT)) {
                throw new RuntimeException(conn.getHostname()+" 命令 "+cmd+ "执行超时 "+timeout);
            }
            return new SshResponse(out.toString(), err.toString(), ce.getExitStatus());
        } finally {
            client.stop();
        }
    }
}

2.2.2.返回数据实体对象

/**
 * SSH应答结果集
 * @author lenny
 * @date 20230101
 */
@Data
public class SshResponse {
    private String stdOutput;
    private String errOutput;
    private int returnCode;

    public SshResponse(String stdOutput, String errOutput, int returnCode) {
        this.stdOutput = stdOutput;
        this.errOutput = errOutput;
        this.returnCode = returnCode;
    }
}

2.2.3.查询终端日志方法

这里使用Cat命令检索文件,并将检索到的文件内容进行处理

    /**
     * 查询终端日志
     * @param terminalNum 终端ID号
     * @param date 日期
     * @return
     */
    public SshResponse queryTerminalLog(String terminalNum, String date){
        try {
            Integer protocolType=808;
            TerminalInfo terminalInfo=terminalDao.queryTerminalInfoByTerminalNum(terminalNum);
            if(terminalInfo!=null&&terminalInfo.getProtocolType()>0){
                protocolType=terminalInfo.getProtocolType();
            }
            SshConnection conn  = new SshConnection(userName,password,host);
            String filter=String.format("grep -E '%s|%s' | grep '%s'", "received:", "downlink command:", terminalNum);
            String command=String.format("cat %s | %s",getTerminalLogPath(protocolType,date),filter);
            SshResponse sshResponse = SSHLinuxUtils.runCommand(conn,command,60);
            handleTerminalLogResponse(sshResponse);
            return sshResponse;
        } catch (Exception e) {
            log.error("queryTerminalLog:terminalId:{},date:{}",terminalNum,date,e);
            return null;
        }
    }


    /**
     * 处理返回的终端日志
     * @param sshResponse
     */
    private void handleTerminalLogResponse(SshResponse sshResponse){
        if(sshResponse.getReturnCode()==0&& StringUtils.isNotBlank(sshResponse.getStdOutput())){
            String terminalLog=sshResponse.getStdOutput();
            String [] terminalLogs = terminalLog.split("\\n");
            List stringList=new ArrayList<>();
            for (String item : terminalLogs) {
                String regex;
                if (item.contains("downlink command:")) {
                    regex ="(?<= INFO).*?(?=downlink command:)";
                } else {
                    regex ="(?<= INFO).*?(?=received:)";
                }
                String result =item.replaceAll(regex,"").replace("INFO","")
                        .replace("downlink command","<<==")
                        .replace("received","==>>");
                stringList.add(result.toUpperCase());
            }
            sshResponse.setStdOutput(JSON.toJSONString(stringList));
        }
    }

2.2.4.封装Controller

    @PostMapping(value = "queryTerminalLog" )
    public Result queryTerminalLog(@RequestBody SshParams sshParams){
        if(sshParams==null||StringUtils.isBlank(sshParams.getTerminalNum())||StringUtils.isBlank(sshParams.getDate())){
            return ResultGenerator.failure(ResultEnum.PARAM_ERROR);
        }
        SshResponse response=sshService.queryTerminalLog(sshParams.getTerminalNum(),sshParams.getDate());
        if(response==null){
            return ResultGenerator.failure();
        }else {
            if (response.getReturnCode() > 0) {
                return ResultGenerator.failure(response.getErrOutput());
            } else {
                return ResultGenerator.success(response.getStdOutput());
            }
        }
    }

里面的方法包含了我系统使用中的一些实体类,这里并未给出,需要针对自己的项目情况做些调整。

3.结束语

对物联网软件感兴趣的朋友,可以加我QQ:571521973。

你可能感兴趣的:(Java,物联网,运维,spring,boot,后端,java,ssh)