This commit is contained in:
tao.chen
2026-09-02 12:31:06 +08:00
commit 6fa600f88c
7 changed files with 2852 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
Generated
+2473
View File
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "rust-learning"
version = "0.1.0"
edition = "2024"
# 显式重命名二进制产物
[[bin]]
name = "toolbox" # 输出的二进制文件名
path = "src/main.rs" # 入口文件路径
[dependencies]
clap = { version = "4.6.6", features = ["derive"] }
flate2 = "1.1.10"
reqwest = { version = "0.13.4", features = ["json"] }
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151"
sysinfo = "0.39.6"
tar = "0.4.46"
tokio = { version = "1.53.1", features = ["full"] }
zip = "8.6.0"
+140
View File
@@ -0,0 +1,140 @@
use clap::{Args, Subcommand};
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::fs::File;
use std::path::{Path, PathBuf};
use tar::Archive as TarArchive;
use tar::Builder as TarBuilder;
use zip::ZipArchive;
#[derive(Args, Debug)]
pub struct ArchiveArgs {
#[command(subcommand)]
pub command: ArchiveCommands,
}
#[derive(Subcommand, Debug)]
pub enum ArchiveCommands {
/// 自动识别格式并解压 (替代 extract)
Extract {
/// 压缩包路径
file: PathBuf,
/// 解压目标目录 (默认为当前目录)
#[arg(short, long)]
target: Option<PathBuf>,
},
/// 打包为 .tar.gz (替代 targz)
Compress {
/// 输出的 .tar.gz 文件名
output: PathBuf,
/// 要压缩的文件或目录列表
files: Vec<PathBuf>,
},
/// 查看压缩包内的内容 (替代 tarls)
List {
/// 压缩包路径
file: PathBuf,
},
}
pub async fn run(args: ArchiveArgs) -> Result<(), Box<dyn std::error::Error>> {
match args.command {
ArchiveCommands::Extract { file, target } => {
let out_dir = target.unwrap_or_else(|| PathBuf::from("."));
extract_archive(&file, &out_dir)?;
}
ArchiveCommands::Compress { output, files } => {
compress_targz(&output, &files)?;
}
ArchiveCommands::List { file } => {
list_archive(&file)?;
}
}
Ok(())
}
// 智能解压实现
fn extract_archive(archive_path: &Path, target_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
if !archive_path.exists() {
return Err(format!("文件不存在: {:?}", archive_path).into());
}
let file_name = archive_path.to_string_lossy().to_lowercase();
println!("📦 正在解压 {:?}{:?} ...", archive_path, target_dir);
if file_name.ends_with(".tar.gz") || file_name.ends_with(".tgz") {
let file = File::open(archive_path)?;
let gz = GzDecoder::new(file);
let mut archive = TarArchive::new(gz);
archive.unpack(target_dir)?;
} else if file_name.ends_with(".zip") {
let file = File::open(archive_path)?;
let mut archive = ZipArchive::new(file)?;
archive.extract(target_dir)?;
} else {
return Err("不支持的压缩包格式!仅支持 .tar.gz, .tgz, .zip".into());
}
println!("✅ 解压完成!");
Ok(())
}
// .tar.gz 压缩实现
fn compress_targz(output_path: &Path, files: &[PathBuf]) -> Result<(), Box<dyn std::error::Error>> {
let tar_gz_file = File::create(output_path)?;
let enc = GzEncoder::new(tar_gz_file, Compression::default());
let mut tar = TarBuilder::new(enc);
for path in files {
if !path.exists() {
eprintln!("警告: 跳过不存在的文件/目录 {:?}", path);
continue;
}
let name = path.file_name().ok_or("无效的文件名")?;
if path.is_dir() {
tar.append_dir_all(name, path)?;
} else {
tar.append_path_with_name(path, name)?;
}
}
tar.finish()?;
println!("打包成功生成: {:?}", output_path);
Ok(())
}
// 查看压缩包列表实现
fn list_archive(archive_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
let file_name = archive_path.to_string_lossy().to_lowercase();
if file_name.ends_with(".tar.gz") || file_name.ends_with(".tgz") {
let file = File::open(archive_path)?;
let gz = GzDecoder::new(file);
let mut archive = TarArchive::new(gz);
println!("{:<10} {:<10} 名称", "权限", "大小");
println!("{}", "-".repeat(40));
for entry in archive.entries()? {
let entry = entry?;
let header = entry.header();
println!("{:<10} {:<10} {:?}", header.mode()?, header.size()?, entry.path()?);
}
} else if file_name.ends_with(".zip") {
let file = File::open(archive_path)?;
let mut archive = ZipArchive::new(file)?;
println!("{:<10} 名称", "大小");
println!("{}", "-".repeat(30));
for i in 0..archive.len() {
let file = archive.by_index(i)?;
println!("{:<10} {}", file.size(), file.name());
}
} else {
return Err("不支持的压缩包格式".into());
}
Ok(())
}
+71
View File
@@ -0,0 +1,71 @@
use clap::Args;
use serde::Serialize;
/// Bark 推送命令的具体参数
#[derive(Args, Debug)]
pub struct BarkArgs {
/// 接口请求地址 URL
#[arg(short, long, default_value = "https://bark.maimaicuizhiji.top/push")]
pub url: String,
/// 设备 Key (device_key)
#[arg(short, long)]
pub device_key: String,
/// 通知标题
#[arg(short, long)]
pub title: String,
/// 通知内容
#[arg(short, long)]
pub body: String,
/// 分组名称
#[arg(short, long, default_value = "backup")]
pub group: String,
/// 是否归档 (1 或 0)
#[arg(long, default_value_t = 1)]
pub is_archive: u8,
/// 存活时间 TTL(秒)
#[arg(long, default_value_t = 3600)]
pub ttl: u64,
}
#[derive(Serialize)]
struct PushPayload<'a> {
device_key: &'a str,
title: &'a str,
body: &'a str,
group: &'a str,
#[serde(rename = "isArchive")]
is_archive: u8,
ttl: u64,
}
/// 执行 Bark 推送逻辑的入口函数
pub async fn run(args: BarkArgs) -> Result<(), Box<dyn std::error::Error>> {
let payload = PushPayload {
device_key: &args.device_key,
title: &args.title,
body: &args.body,
group: &args.group,
is_archive: args.is_archive,
ttl: args.ttl,
};
let client = reqwest::Client::new();
let response = client.post(&args.url).json(&payload).send().await?;
if response.status().is_success() {
let resp_text = response.text().await?;
println!("Bark 推送成功: {}", resp_text);
} else {
eprintln!("Bark 推送失败,状态码: {}", response.status());
let err_text = response.text().await?;
eprintln!("错误信息: {}", err_text);
}
Ok(())
}
+41
View File
@@ -0,0 +1,41 @@
mod bark;
mod nettool;
mod archive;
use clap::{Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(name = "toolbox", version, about = "多功能集成运维 CLI 工具")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// 发送 Bark 推送通知
Bark(bark::BarkArgs),
/// 网络运维诊断工具
Nettool(nettool::NetToolArgs),
/// 文件归档与解压工具
Archive(archive::ArchiveArgs),
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
match cli.command {
Commands::Bark(args) => {
bark::run(args).await?;
}
Commands::Nettool(args) => {
nettool::run(args).await?;
}
Commands::Archive(args) => {
archive::run(args).await?;
}
}
Ok(())
}
+106
View File
@@ -0,0 +1,106 @@
use clap::{Args, Subcommand};
use std::net::{TcpStream, ToSocketAddrs};
use std::time::Duration;
use sysinfo::{ProcessesToUpdate, System};
#[derive(Args, Debug)]
pub struct NetToolArgs {
#[command(subcommand)]
pub command: NetCommands,
}
#[derive(Subcommand, Debug)]
pub enum NetCommands {
/// 查看端口占用情况
Port {
/// 要查询的端口号
port: u16,
},
/// 测试目标地址端口连通性
Probe {
/// 目标主机 (IP 或域名)
host: String,
/// 目标端口
port: u16,
/// 超时时间(毫秒)
#[arg(short, long, default_value_t = 2000)]
timeout: u64,
},
/// 获取本机公网 IP
Myip,
}
pub async fn run(args: NetToolArgs) -> Result<(), Box<dyn std::error::Error>> {
match args.command {
NetCommands::Port { port } => {
check_port(port);
}
NetCommands::Probe { host, port, timeout } => {
test_probe(&host, port, timeout);
}
NetCommands::Myip => {
fetch_my_ip().await;
}
}
Ok(())
}
fn check_port(port: u16) {
let mut sys = System::new_all();
sys.refresh_processes(ProcessesToUpdate::All, true);
println!("正在查询端口 {} 的占用情况...", port);
let mut found = false;
for (pid, process) in sys.processes() {
if process.name().to_string_lossy().contains("listen") {
println!("PID: {:<8} 进程名: {}", pid, process.name().to_string_lossy());
found = true;
}
}
if !found {
println!("未发现端口 {} 的相关进程,或需要更高权限。", port);
}
}
fn test_probe(host: &str, port: u16, timeout_ms: u64) {
let addr_str = format!("{}:{}", host, port);
println!("正在探测 {} ...", addr_str);
match addr_str.to_socket_addrs() {
Ok(mut addrs) => {
if let Some(addr) = addrs.next() {
let timeout = Duration::from_millis(timeout_ms);
match TcpStream::connect_timeout(&addr, timeout) {
Ok(_) => println!("连接成功![{}]", addr),
Err(e) => eprintln!("连接失败: {}", e),
}
} else {
eprintln!("域名无法解析");
}
}
Err(e) => eprintln!("地址解析错误: {}", e),
}
}
async fn fetch_my_ip() {
let providers = [
"https://api.ipify.org",
"https://ifconfig.me/ip",
"https://icanhazip.com",
];
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(3))
.build()
.unwrap();
for url in providers {
if let Ok(resp) = client.get(url).send().await {
if let Ok(ip) = resp.text().await {
println!("Public IP: {}", ip.trim());
return;
}
}
}
eprintln!("获取公网 IP 失败,请检查网络设置。");
}