嗨,您好 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
文章所在专栏:Linux
我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
向我询问任何您想要的东西,ID:vnjohn
觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客
代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏
目录
结合前后端交互来说,为了能够在前端页面上展示出服务器指定目录下所有的包文件(文件由 Jenkins 自动化编译打包后产生)并提供下载 copy 功能
基于 Android、IOS 打包所生成 apk、ipa 文件都会存放在我们的服务器目录中进行备份,然后在 PC 后台可以对我们这些包文件进行版本和规划管理,所以需要实现这样的一个功能
对这部分工作的操作而言,需要引入第三方依赖来进行协同开发,主要是为我们提供 sshd 工具,实现 Linux 主机互连,读取可访问的文件目录
<dependency>
<groupId>ch.ethz.ganymedgroupId>
<artifactId>ganymed-ssh2artifactId>
<version>build210version>
dependency>
首先需要与 Linux 服务器开启登录会话,建立双方的连接,才能读取到服务器上指定下的目录文件
private static Connection CONN = null;
private static String DEFAULT_CHART = "UTF-8";
private static String HOSTNAME;
private static String USERNAME;
private static String PASSWORD;
@Value("${ssh.hostname:}")
public void setHostname(String name) {
HOSTNAME = name;
}
@Value("${ssh.username:}")
public void setUsername(String user) {
USERNAME = user;
}
@Value("${ssh.password:}")
public void setPassword(String pwd) {
PASSWORD = pwd;
}
@PostConstruct
public void init() {
// 创建远程连接,默认连接端口为 22,如果不使用默认,可以使用方法 Connection(String hostname, int port)
CONN = new Connection(HOSTNAME);
try {
// 连接远程服务器
CONN.connect();
// 使用用户名和密码登录
CONN.authenticateWithPassword(USERNAME, PASSWORD);
} catch (IOException e) {
log.info("主机名:{},用户名:{},密码:{}", HOSTNAME, USERNAME, PASSWORD);
log.error("连接主机失败,", e);
}
}
以上呈现的两个知识点:
@Value 注解构造 static 静态变量的问题,如果将 @Value 注解直接放在属性的上面是无法生效的,如:
@Value("${ssh.hostname}")
private static String hostname;
加上 static 修饰是不会生效的,正确写法是:
@Value("${ssh.hostname}")
public void setHostname(String name) {
hostname = name;
}
主机名、用户名、密码最好是采用加密的方式进行处理,保存在配置文件中,然后在这块建立连接的时候通过加密算法进行相对应的解密,以确保账号信息不外泄,保证数据安全性
/**
* 当前所要执行的命令
*
* @param cmd 命令指令
* @return 执行命令 返回结果
*/
public static String execute(String cmd) {
String result = "";
Session session = null;
try {
if (CONN != null) {
// 打开一个会话
session = CONN.openSession();
// 执行命令
session.execCommand(cmd);
result = processStdout(session.getStdout(), DEFAULT_CHART);
log.info("执行的命令:{}", cmd);
}
return result;
} catch (IOException e) {
log.info("执行命令失败,链接 conn:{},执行的命令:{},执行后的结果:{}", CONN, cmd, e.getMessage());
e.printStackTrace();
return "执行失败";
} finally {
if (session != null) {
session.close();
}
}
}
/**
* 执行命令返回的结果(返回结果需要等待所有结果返回完毕才返回)
*
* @param in 流对象
* @param charset 编码格式
* @return
*/
private static String processStdout(InputStream in, String charset) {
InputStream stdout = new StreamGobbler(in);
StringBuffer buffer = new StringBuffer();
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(stdout, charset));
String line;
while ((line = br.readLine()) != null) {
buffer.append(line).append("\n");
}
} catch (IOException e) {
log.error("执行命令异常,", e);
} finally {
try {
stdout.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return buffer.toString();
}
将所要执行的命令,通过前面所建立的会话执行,处理返回在 Linux 执行后返回的输出流结果,格式为一个通道建立好的流对象:ChannelInputStream,我们只要关注处理它返回的结果即可.
可访问的文件目录应该也是要配置好访问权限,每个指定的文件或目录应该由特定的用户进行管理,避免权限过大,对服务器的其他信息进行误操作,影响到服务器全局的安全性
/**
* 获取指定目录下的所有子目录和文件列表
*
* @param cmd 执行的命令
* @param firstPath 要筛选的父目录
* @return 可阅览的目录、文件列表
*/
public static List<ServerFile> getFolderAndFile(String cmd, String firstPath) {
String result = execute(cmd);
String[] resultArray = result.split("\n");
String[] newArray = delete(0, resultArray);
if (newArray.length == 0) {
return Collections.emptyList();
}
List<ServerFile> readResult = Arrays.stream(newArray).map(str -> {
ServerFile serverFile = new ServerFile();
// 判断当前行遍历是否是目录级
boolean flag = str.startsWith("d");
String fileName = str.substring(str.lastIndexOf(" ") + 1);
serverFile.setFolderFlag(flag);
serverFile.setFileName(fileName);
serverFile.setPath(firstPath + "/" + fileName);
if (flag) {
// 如果当前行是目录那么需要继续递归当前方法查找它下面的文件或目录
List<ServerFile> folderAndFile = getFolderAndFile(cmd + "/" + fileName, serverFile.getPath());
serverFile.setChildren(folderAndFile);
}
return serverFile;
}).collect(Collectors.toList());
return readResult;
}
/**
* 此方法为了删除数组中第一个元素 重构数组并返回,第一行数据为统计数量,故需要进行移除
*
* [root@trench android]# ls -l
* total 0
* -rw-r--r-- 1 root root 0 Nov 1 10:30 1
* -rw-r--r-- 1 root root 0 Nov 1 10:30 2
*
*
* @return 返回目录、文件数组
*/
public static String[] delete(int index, String[] array) {
// 数组的删除其实就是覆盖前一位
String[] arrNew = new String[array.length - 1];
for (int i = 0; i < array.length - 1; i++) {
if (i < index) {
arrNew[i] = array[i];
} else {
arrNew[i] = array[i + 1];
}
}
return arrNew;
}
这是最终由接口或单元测试类调用的方法,传入所要执行的命令以及所要渲染的首路径:所要渲染的目录
在配置中将属性进行传递,hotstname、username、password 三个参数,作为你的虚拟机或真实云服务器上的公网 IP、用户名、密码
创建一个演示目录以及两个空白文件,如下:
[root@trench android]# pwd
/var/android
[root@trench android]# ls -l
total 0
-rw-r--r-- 1 root root 0 Nov 1 10:30 1
-rw-r--r-- 1 root root 0 Nov 1 10:30 2
那我在单元测试内传入的参数,1-执行命令「ls -l」2-所要筛选的路径「/var/android」
执行以后,结果如下:
在命令行窗口会输出如下内容:
红色框框是输出的内容,输出的目录和文件有特定标识,目录前面会有 一个 d 关键字,输出内容的第一行是总大小,这一行需要被忽略,这是工具类 delete 方法起到的作用
getFolderAndFile 该方法是将命令所查到的目录、文件整理成一个树形菜单,前端显示的时候需要写入一个接口调用这个方法
该篇博文作为一个简单的实践操作,通过 Java 代码操作 Linux 命令行,首先是要依赖一个对应的依赖,然后建立与 Linux 客户端的会话连接,通过该会话来执行命令—查询目录文件,运用的场景:通过 PC 后台管理安卓、IOS 打包好文件,统一规范版本管理,希望这篇博文能够让你借鉴起来运用到其他的场景中
愿你我都能够在寒冬中相互取暖,互相成长,只有不断积累、沉淀自己,后面有机会自然能破冰而行!
博文放在 Linux 专栏里,欢迎订阅,会持续更新!
如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!
推荐专栏:Spring、MySQL,订阅一波不再迷路
大家的「关注❤️ + 点赞 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!