开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
前言
这两天了解了一下tauri,为了能更快地了解它,就用它做了一个小小的功能:通过输入的端口号查看计算机上的进程信息。这个需求衍生于开发人员经常会遇到端口号被某一个进程占用了,需要通过指定的命令查看目标进程:lsof -i :端口号,接下来就记录一下我是怎么使用tauri实现这个功能的。
初始化tauri项目
因为tauri依赖于rust,所以需要先安装好rust,在此基础上,我们根据官方文档使用cargo来创建tauri app,就两行命令:
cargo install create-tauri-app
cargo create-tauri-app
这两行命令执行完毕后,通过clion或者idea导入项目。
在项目导入后,需要通过命令编译后才能启动,可以在项目文件夹所在的终端执行以下命令:
cargo tauri dev
接下来等待计算机编译完成后,会启动一个类似下面的tauri app,那么我们的项目初始化就成功了。
添加新功能
- 前端代码
为了能够在前端输入端口号,我们需要修改一下index.html,我们直接拷贝一下输入框和按钮的代码:
<div>
<input id="port-input" placeholder="输入一个端口号..." />
<button id="port-button" type="button">查询进程</button>
</div>
我们分别调整一下对应的id属性为port-input和port-button;
下面我们还需要把main.js也做一定的修改,我们需要给新增的按钮加上click事件,另外就是获取输入框port-input元素:
const {invoke} = window.__TAURI__.tauri;
let portInputEl;
let greetInputEl;
let greetMsgEl;
async function greet() {
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
greetMsgEl.textContent = await invoke("greet", {name: greetInputEl.value});
}
// 调用rust提供的方法获取进程列表
async function get_process_by_port() {
invoke("get_process_by_port", {port: parseInt(portInputEl.value)})
.then((res) => {
render_process_list(greetMsgEl, res);
});
}
function render_process_list(element, res) {
// TODO
// 渲染列表
}
window.addEventListener("DOMContentLoaded", () => {
// 获取input元素
portInputEl = document.querySelector("#port-input");
greetInputEl = document.querySelector("#greet-input");
greetMsgEl = document.querySelector("#greet-msg");
document
.querySelector("#greet-button")
.addEventListener("click", () => greet());
// 监听click事件
document.querySelector("#port-button")
.addEventListener("click", () => get_process_by_port());
});
- rust代码
在rust代码中,我们需要实现通过调用shell命令的方式,获取查询出来的结果,并返回给前端。
我们先看一下在终端中执行的结果:
上述脚步通过rust调用,代码如下:
use std::process::Command;
use serde::{Serialize, Deserialize};
// 构建结构体,用来包装结果
#[derive(Serialize, Deserialize, Debug)]
struct ProcessInfo {
command: String,
pid: String,
user: String,
fd: String,
ip_type: String,
device: String,
size_off: String,
node: String,
name: String,
}
#[tauri::command]
fn get_process_by_port(port: isize) -> Result<Vec<ProcessInfo>, String> {
let output = Command::new("lsof")
.arg("-i")
.arg(format!(":{}", port))
.output().unwrap();
// 获取输出结果
let shell_output = String::from_utf8(output.stdout).unwrap();
// 根据换行符分割
let vec: Vec<&str> = shell_output.split("\n").collect();
let mut result: Vec<ProcessInfo> = vec![];
// 循环遍历,丢弃第一行title和最后一行空字符串
for (i, v) in vec.iter().enumerate() {
if i <= 0 || i == vec.len() - 1 {
continue;
}
// 根据空格分割,把每一个字段对应的值找出来
let values: Vec<&str> = v.split_whitespace().collect::<Vec<&str>>();
// 包装成ProcessInfo对象并放进数组中
result.push(
ProcessInfo {
command: values[0].into(),
pid: values[1].into(),
user: values[2].into(),
fd: values[3].into(),
ip_type: values[4].into(),
device: values[5].into(),
size_off: values[6].into(),
node: values[7].into(),
name: values[8].into(),
}
);
}
// 返回结果
Ok(result)
}
因为我们通过shell命令返回的是一整串字符串,所以上述代码大部分都是在把字符串处理成目标对象。剩下的就是把get_process_by_port方法通过main注册到invoke_handler中:
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, get_process_by_port])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
编译运行
前后端代码都准备好后,我们开始通过cargo tauri dev命令执行项目编译,这个需要等待一段时间。当项目编译完毕启动后,界面如下:
输入端口号8080,和我们在终端得到的结果一致,说明我们的第一个小功能成功了!