init
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Generated
+2473
File diff suppressed because it is too large
Load Diff
+20
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 失败,请检查网络设置。");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user