feat: Initialize UPFS project with login and file upload functionality
- Add Cargo.toml with dependencies for reqwest, serde, tokio, and clap. - Implement login functionality to retrieve authentication token. - Create a module for handling file uploads with multipart support. - Set up command-line interface for file upload operations. - Include error handling for file existence and HTTP requests.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
1613
Cargo.lock
generated
Normal file
1613
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "upfs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.11", features = ["json", "multipart"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
clap = { version = "4.0", features = ["derive"] }
|
||||||
48
src/login/get_token.rs
Normal file
48
src/login/get_token.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
use reqwest;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct User {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct LoginResponse {
|
||||||
|
code: i32,
|
||||||
|
message: String,
|
||||||
|
data: LoginData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct LoginData {
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn login_and_get_token(username: String, password: String) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let user = User {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.post("http://192.168.1.56:5255/api/auth/login")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.json(&user)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let login_response: LoginResponse = response.json().await?;
|
||||||
|
|
||||||
|
if login_response.code == 200 {
|
||||||
|
Ok(login_response.data.token)
|
||||||
|
} else {
|
||||||
|
Err(format!("Login failed: {}", login_response.message).into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(format!("HTTP request failed with status: {}", response.status()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/login/mod.rs
Normal file
3
src/login/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod get_token;
|
||||||
|
|
||||||
|
pub use get_token::{login_and_get_token};
|
||||||
83
src/main.rs
Normal file
83
src/main.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
mod login;
|
||||||
|
mod update;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use login::login_and_get_token;
|
||||||
|
use update::upload_file;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "upfs")]
|
||||||
|
#[command(about = "Upload file to UPFS server")]
|
||||||
|
struct Cli {
|
||||||
|
/// File path to upload
|
||||||
|
#[arg(short, long)]
|
||||||
|
file: String,
|
||||||
|
|
||||||
|
/// Remote file path on server
|
||||||
|
#[arg(short, long)]
|
||||||
|
remote_path: String,
|
||||||
|
|
||||||
|
/// Username for authentication
|
||||||
|
#[arg(short, long, default_value = "admin")]
|
||||||
|
username: String,
|
||||||
|
|
||||||
|
/// Password for authentication
|
||||||
|
#[arg(short, long)]
|
||||||
|
password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
if !std::path::Path::new(&cli.file).exists() {
|
||||||
|
eprintln!("错误: 文件 '{}' 不存在", cli.file);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取密码(如果没有提供则询问)
|
||||||
|
let password = match cli.password {
|
||||||
|
Some(pwd) => pwd,
|
||||||
|
None => {
|
||||||
|
println!("请输入密码:");
|
||||||
|
let mut input = String::new();
|
||||||
|
std::io::stdin().read_line(&mut input).expect("读取密码失败");
|
||||||
|
input.trim().to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("正在登录服务器...");
|
||||||
|
|
||||||
|
// 登录获取token
|
||||||
|
let token = match login_and_get_token(cli.username, password).await {
|
||||||
|
Ok(token) => {
|
||||||
|
println!("登录成功!");
|
||||||
|
token
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("登录失败: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("正在上传文件: {} 到远程路径: {}", cli.file, cli.remote_path);
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
|
match upload_file(token, &cli.file, &cli.remote_path).await {
|
||||||
|
Ok((true, response)) => {
|
||||||
|
println!("✅ 文件上传成功!");
|
||||||
|
println!("服务器响应: {}", response);
|
||||||
|
}
|
||||||
|
Ok((false, response)) => {
|
||||||
|
println!("❌ 文件上传失败!");
|
||||||
|
println!("服务器响应: {}", response);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("上传过程中发生错误: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/update/form.rs
Normal file
60
src/update/form.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use reqwest;
|
||||||
|
use reqwest::multipart;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UploadResponse {
|
||||||
|
pub status: reqwest::StatusCode,
|
||||||
|
pub text: String,
|
||||||
|
pub success: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn upload_file_with_token(
|
||||||
|
token: String,
|
||||||
|
file_path: &str,
|
||||||
|
remote_path: &str,
|
||||||
|
) -> Result<UploadResponse, Box<dyn std::error::Error>> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// Create multipart form
|
||||||
|
let file_content = tokio::fs::read(file_path).await?;
|
||||||
|
let file_name = Path::new(file_path)
|
||||||
|
.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.unwrap_or("file");
|
||||||
|
|
||||||
|
let file_part = multipart::Part::bytes(file_content)
|
||||||
|
.file_name(file_name.to_string());
|
||||||
|
|
||||||
|
let form = multipart::Form::new()
|
||||||
|
.part("file", file_part);
|
||||||
|
|
||||||
|
// Send PUT request
|
||||||
|
let response = client
|
||||||
|
.put("http://192.168.1.56:5255/api/fs/form")
|
||||||
|
.header("Authorization", token)
|
||||||
|
.header("File-Path", remote_path)
|
||||||
|
.multipart(form)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let status = response.status();
|
||||||
|
let text = response.text().await?;
|
||||||
|
let success = status.is_success();
|
||||||
|
|
||||||
|
Ok(UploadResponse {
|
||||||
|
status,
|
||||||
|
text,
|
||||||
|
success,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenient function that directly returns success status and response text
|
||||||
|
pub async fn upload_file(
|
||||||
|
token: String,
|
||||||
|
file_path: &str,
|
||||||
|
remote_path: &str,
|
||||||
|
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
||||||
|
let result = upload_file_with_token(token, file_path, remote_path).await?;
|
||||||
|
Ok((result.success, result.text))
|
||||||
|
}
|
||||||
3
src/update/mod.rs
Normal file
3
src/update/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod form;
|
||||||
|
|
||||||
|
pub use form::upload_file;
|
||||||
Reference in New Issue
Block a user