欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用Rust語言搞定圖片上傳功能的示例詳解

 更新時(shí)間:2025年08月03日 09:49:40   作者:林太白  
這篇文章主要為大家詳細(xì)介紹了如何使用Rust語言搞定圖片上傳功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下

1、下載引入

Cargo.toml安裝依賴

已經(jīng)有的就不需要額外添加了

這里我額外移入了uuid 生成唯一文件名

[dependencies]
actix-web = "4.0"  # 開發(fā) RESTful API接口
actix-multipart = "0.4" # 處理文件上傳
tokio = { version = "1", features = ["full"] } # 異步運(yùn)行時(shí),網(wǎng)絡(luò)、文件I/O異步任務(wù)
futures = "0.3" # 異步編程 
serde = { version = "1.0", features = ["derive"] } # 序列化和反序列化
serde_json = "1.0" # 幫我們生成文件名
uuid = "1.0"   # 幫我們生成文件名
mime_guess = "2.0" # 猜測文件類型


更換為下面的 2025-08-01
uuid = { version = "1.17.0", features = ["v4"] }


// 添加文件依賴
  actix-files = "0.6.2"  # 靜態(tài)文件服務(wù)

uuid最新版本地址:https://crates.io/crates/uuid

2、使用

入口申明模塊

路由入口,我們新建一個(gè)upload模塊

main.rs文件之中申明模塊

HttpServer::new(move || {
        let cors = Cors::default()
            .allow_any_origin()
            .allow_any_method()
            .allow_any_header(); // 允許所有來源
        App::new()
            // 添加 CORS 中間件
            .wrap(cors)
            // 2. 注入數(shù)據(jù)庫連接池
            .app_data(web::Data::new(pool.clone()))
            // 3. 注冊模塊路由加前綴
            .service(
                web::scope("/api") // 這里加上 /api 前綴
                    .configure(modules::user::routes::config),
                    .configure(modules::upload::routes::config),
            )
            // 3. 注冊路由
            .route("/", web::get().to(welcome))
    })

申明模塊入口

這里需要申明外層模塊和子模塊兩個(gè)部分

src\modules\mod.rs

pub mod upload;
pub mod user;


src\modules\upload\mod.rs
pub mod handlers;
pub mod routes; // 必須有這一行,否則無法使用路由

routes.rs模塊之中添加接口

routes.rs模塊添加接口

use actix_web::web;
pub fn config(cfg: &mut web::ServiceConfig) {
  cfg.route("/upload/image", web::post().to(crate::modules::upload::handlers::uploadimg));
}

測試接口邏輯

handlers.rs之中處理方法邏輯,編寫我們的上傳,這里我們先測試一下

handlers.rs

// # 處理函數(shù)(可選)
use actix_web::{web, HttpRequest, HttpResponse, Responder};
use sqlx::{MySqlPool,MySql, Pool};
use crate::common::response::ApiResponse;// 導(dǎo)入 ApiResponse 模型

// 上傳圖片接口
pub async fn uploadimg() -> HttpResponse {
    HttpResponse::Ok().json(ApiResponse {
        code: 200,
        msg: "接口信息",
        data:  None::<()>,
    })
}

測試我們的接口,返回如下

{
    "code": 200,
    "msg": "接口信息"
}

3、功能實(shí)現(xiàn)

上傳文件邏輯

接下來我們參考我們之前的接口部分,返回上傳圖片成功以后的數(shù)據(jù),這里我們先以實(shí)現(xiàn)功能為主

// // # 處理函數(shù)(可選)
// use actix_web::{web, HttpRequest, HttpResponse, Responder};
// use sqlx::{MySqlPool,MySql, Pool};

// // 上傳圖片接口
// pub async fn uploadimg() -> HttpResponse {
//     HttpResponse::Ok().json(ApiResponse {
//         code: 200,
//         msg: "接口信息",
//         data:  None::<()>,
//     })
// }


use actix_web::{web, HttpResponse, Responder};
use actix_multipart::Multipart;
use futures::StreamExt;
use std::fs::{create_dir_all, File};
use std::io::Write;
use uuid::Uuid;
use crate::common::response::ApiResponse;
use std::env;
use std::path::Path;

// 定義響應(yīng)數(shù)據(jù)結(jié)構(gòu)
#[derive(serde::Serialize)]
struct UploadResponse {
    fullPath: String,
    relativePath: String,
    size: u64,
    fileName: String,
    fileType: String,
    fileUid: String,
}

const UPLOAD_DIR: &str = "./uploads";
const ALLOWED_MIME_TYPES: [&str; 3] = ["image/jpeg", "image/png", "image/gif"];

pub async fn upload_img(mut payload: Multipart) -> impl Responder {
    // 創(chuàng)建上傳目錄(如果不存在)
    if let Err(e) = create_dir_all(UPLOAD_DIR) {
        return internal_server_error(&format!("創(chuàng)建目錄失敗: {}", e));
    }

    // 獲取基礎(chǔ)URL(從環(huán)境變量或使用默認(rèn)值)
    let base_url = env::var("BASE_URL")
        .unwrap_or_else(|_| "http://localhost:8080".to_string());

    // 遍歷多部分表單字段
    while let Some(field_result) = payload.next().await {
        let mut field = match field_result {
            Ok(f) => f,
            Err(e) => return bad_request(&format!("字段解析失敗: {}", e)),
        };

        // 獲取內(nèi)容處置頭部
        let content_disposition = field.content_disposition();
        
        // 獲取文件名
        let original_file_name = match content_disposition.get_filename() {
            Some(name) => name.to_string(),
            None => continue,  // 跳過非文件字段
        };

        // 驗(yàn)證文件類型
        let mime_type = field.content_type().to_string();
        if !ALLOWED_MIME_TYPES.contains(&mime_type.as_str()) {
            return bad_request("只允許 JPEG、PNG 或 GIF 圖片");
        }

        // 生成唯一文件名和路徑
        let extension = get_extension(&mime_type);
        let file_id = Uuid::new_v4().to_string();
        let unique_name = format!("{}.{}", file_id, extension);
        let file_path = format!("{}/{}", UPLOAD_DIR, unique_name);
        let relative_path = format!("/uploads/{}", unique_name);
        let absolute_path = format!("{}{}", base_url, relative_path);

        // 保存文件內(nèi)容并獲取文件大小
        let file_size = match save_file(&mut field, &file_path).await {
            Ok(size) => size,
            Err(e) => {
                return internal_server_error(&format!("文件保存失敗: {}", e));
            }
        };

        // 創(chuàng)建響應(yīng)數(shù)據(jù)
        let response_data = UploadResponse {
            fullPath: absolute_path,
            relativePath: relative_path,
            size: file_size,
            fileName: format!("圖片-{}", original_file_name),
            fileType: mime_type,
            fileUid: file_id,
        };

        // 返回成功響應(yīng)
        return HttpResponse::Ok().json(ApiResponse {
            code: 200,
            msg: "圖片上傳成功",
            data: Some(response_data),
        });
    }

    // 沒有找到有效的文件字段
    bad_request("未檢測到上傳的文件")
}

/// 根據(jù) MIME 類型獲取文件擴(kuò)展名
fn get_extension(mime_type: &str) -> &str {
    match mime_type {
        "image/jpeg" => "jpg",
        "image/png" => "png",
        "image/gif" => "gif",
        _ => "bin", // 不會(huì)發(fā)生(前面已驗(yàn)證)
    }
}

/// 保存上傳的文件并返回文件大小
async fn save_file(field: &mut actix_multipart::Field, path: &str) -> std::io::Result<u64> {
    let mut file = File::create(path)?;
    let mut total_size = 0;
    
    // 處理每個(gè)數(shù)據(jù)塊
    while let Some(chunk_result) = field.next().await {
        // 處理可能的 MultipartError
        let chunk = chunk_result.map_err(|e| {
            std::io::Error::new(
                std::io::ErrorKind::Other, 
                format!("讀取數(shù)據(jù)塊失敗: {}", e)
            )
        })?;
        
        // 寫入文件并更新大小
        file.write_all(&chunk)?;
        total_size += chunk.len() as u64;
    }
    
    file.flush()?;
    Ok(total_size)
}

/// 400 錯(cuò)誤響應(yīng)
fn bad_request(msg: &str) -> HttpResponse {
    HttpResponse::BadRequest().json(ApiResponse::<()> {
        code: 400,
        msg:"錯(cuò)誤",
        data: None,
    })
}

/// 500 錯(cuò)誤響應(yīng)
fn internal_server_error(msg: &str) -> HttpResponse {
    HttpResponse::InternalServerError().json(ApiResponse::<()> {
        code: 500,
        msg:"錯(cuò)誤",
        data: None,
    })
}

測試上傳圖片接口

測試接口這個(gè)時(shí)候給我們返回的數(shù)據(jù)如下

{
    "code": 200,
    "msg": "圖片上傳成功",
    "data": {
        "fullPath": "http://localhost:8888/uploads/68007a03-497e-4982-8316-10881289cb1e.png",
        "relativePath": "/uploads/68007a03-497e-4982-8316-10881289cb1e.png",
        "size": 10739,
        "fileName": "圖片-imgjiance2.png",
        "fileType": "image/png",
        "fileUid": "68007a03-497e-4982-8316-10881289cb1e"
    }
}

文件歸位

現(xiàn)在可以看到我們傳入的文件都在upload下,我們分配一下,圖片和視頻區(qū)別后面

pub async fn upload_img(mut payload: Multipart) -> impl Responder {
    // 創(chuàng)建圖片存儲(chǔ)目錄(如果不存在)
    let image_dir = format!("{}/{}", BASE_UPLOAD_DIR, IMAGE_SUBDIR);
    if let Err(e) = create_dir_all(&image_dir) {
        return internal_server_error(&format!("創(chuàng)建目錄失敗: {}", e));
    }

    // 獲取基礎(chǔ)URL(從環(huán)境變量或使用默認(rèn)值)
    let base_url = env::var("BASE_URL")
        .unwrap_or_else(|_| "http://localhost:3000".to_string());

    // 遍歷多部分表單字段
    while let Some(field_result) = payload.next().await {
        let mut field = match field_result {
            Ok(f) => f,
            Err(e) => return bad_request(&format!("字段解析失敗: {}", e)),
        };

        // 獲取內(nèi)容處置頭部
        let content_disposition = field.content_disposition();
        
        // 獲取文件名
        let original_file_name = match content_disposition.get_filename() {
            Some(name) => name.to_string(),
            None => continue,  // 跳過非文件字段
        };

        // 驗(yàn)證文件類型
        let mime_type = field.content_type().to_string();
        if !ALLOWED_MIME_TYPES.contains(&mime_type.as_str()) {
            return bad_request("只允許 JPEG、PNG 或 GIF 圖片");
        }

        // 生成唯一文件名和路徑
        let extension = get_extension(&mime_type);
        let file_id = Uuid::new_v4().to_string();
        let unique_name = format!("{}.{}", file_id, extension);
        
        // 文件存儲(chǔ)路徑(包含子目錄)
        let file_path = format!("{}/{}", image_dir, unique_name);
        
        // URL 路徑(包含子目錄)
        let relative_path = format!("/uploads/{}/{}", IMAGE_SUBDIR, unique_name);
        let absolute_path = format!("{}{}", base_url, relative_path);

        // 保存文件內(nèi)容并獲取文件大小
        let file_size = match save_file(&mut field, &file_path).await {
            Ok(size) => size,
            Err(e) => {
               return internal_server_error(&format!("文件保存失敗: {}", e));
            }
        };

        // 創(chuàng)建響應(yīng)數(shù)據(jù)
        let response_data = UploadResponse {
            fullPath: absolute_path,
            relativePath: relative_path,
            size: file_size,
            fileName: format!("圖片-{}", original_file_name),
            fileType: mime_type,
            fileUid: file_id,
        };

        // 返回成功響應(yīng)
        return HttpResponse::Ok().json(ApiResponse {
            code: 200,
            msg: "圖片上傳成功".to_string(),
            data: Some(response_data),
        });
    }

    // 沒有找到有效的文件字段
    bad_request("未檢測到上傳的文件")
}

這個(gè)時(shí)候返回的接口地址,已經(jīng)成為我們想要的路徑了

{
    "code": 200,
    "msg": "圖片上傳成功",
    "data": {
        "fullPath": "http://localhost:8888/uploads/images/a8d23e18-7155-429e-aea2-5c0f70a545e5.png",
        "relativePath": "/uploads/images/a8d23e18-7155-429e-aea2-5c0f70a545e5.png",
        "size": 10739,
        "fileName": "圖片-imgjiance2.png",
        "fileType": "image/png",
        "fileUid": "a8d23e18-7155-429e-aea2-5c0f70a545e5"
    }
}

文件靜態(tài)路徑

但是訪問我們的圖片地址,卻無法訪問,這是為什么呢?

這是因?yàn)槲覀兎?wù)器上還沒有裝靜態(tài)文件服務(wù),在我們跟入口文件之中配置

依賴前提必須安裝這個(gè)依賴

// 依賴前提
actix-files = "0.6.2"  # 靜態(tài)文件服務(wù)

在主文件之中引入

// 文件服務(wù)
use actix_files as fs;
use std::fs::create_dir_all; // 創(chuàng)建目錄

入口文件之中添加我們的靜態(tài)文件地址

async fn main() -> std::io::Result<()> {
    dotenv().ok(); // 一定要在讀取環(huán)境變量之前調(diào)用

    // 顯式設(shè)置日志級別和輸出格式
    Builder::new()
        .parse_filters("info") // 設(shè)置日志級別為 info
        .init(); // 初始化日志記錄器

    
    info!("日志系統(tǒng)已初始化!??!");
    
    // 確保上傳目錄存在
    let upload_dirs = [
        "./uploads",
        "./uploads/images",
        "./uploads/documents",
        "./uploads/videos",
        "./uploads/others"
    ];
    for dir in &upload_dirs {
        if let Err(e) = create_dir_all(dir) {
            eprintln!("創(chuàng)建目錄 {} 失敗: {}", dir, e);
            // 生產(chǎn)環(huán)境中可能需要更嚴(yán)格的處理
        }
    }
    info!("圖片服務(wù)器已準(zhǔn)備!");


    // 1. 初始化數(shù)據(jù)庫連接池
    // let database_url = env::var("DATABASE_URL").expect("DATABASE_URL not set");
    // 創(chuàng)建 MySQL 異步連接池
    // let pool = MySqlPool::connect(&database_url).await.expect("連接數(shù)據(jù)庫失敗");

    let database_url = env::var("DATABASE_URL").unwrap(); // 獲取數(shù)據(jù)庫連接字符串
    let pool = MySqlPool::connect(&database_url).await.unwrap();
    
    HttpServer::new(move || {
        let cors = Cors::default()
            .allow_any_origin()
            .allow_any_method()
            .allow_any_header(); // 允許所有來源
        App::new()
            // 添加 CORS 中間件
            .wrap(cors)
            // 2. 注入數(shù)據(jù)庫連接池
            .app_data(web::Data::new(pool.clone()))
            // 3. 注冊模塊路由加前綴
            .service(
                fs::Files::new("/uploads/images", "./uploads/images")
                    .prefer_utf8(true)
                    .show_files_listing() // 開發(fā)環(huán)境使用,生產(chǎn)環(huán)境應(yīng)移除
            )
            // 可以添加其他靜態(tài)文件目錄
            .service(
                fs::Files::new("/uploads/documents", "./uploads/documents")
            )
            .service(
                web::scope("/api") // 這里加上 /api 前綴
                    .configure(modules::user::routes::config)
                    .configure(modules::upload::routes::config)
            )
            // 3. 注冊路由
            .route("/", web::get().to(welcome))
    })
    .bind("0.0.0.0:8888")?
    .run()
    .await
}

靜態(tài)資源目錄搭建好了以后,再次訪問,我們的圖片可以完美展示啦

快來跟我一起體驗(yàn)Rust之美吧,最近幾天都寫的很難,所幸都攻克了 簡單但是可能我是小白 很多問題總算踩過去了

到此這篇關(guān)于使用Rust語言搞定圖片上傳功能的示例詳解的文章就介紹到這了,更多相關(guān)Rust圖片上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入理解Rust中Cargo的使用

    深入理解Rust中Cargo的使用

    本文主要介紹了深入理解Rust中Cargo的使用,Cargo簡化了項(xiàng)目的構(gòu)建過程,提供了依賴項(xiàng)管理,以及一系列方便的工作流程工具,下面就來具體的介紹一下如何使用,感興趣的可以了解一下
    2024-04-04
  • 淺談Rust中聲明可見性

    淺談Rust中聲明可見性

    在Rust編程語言中,聲明可見性是一個(gè)核心概念,本文主要介紹了Rust中聲明可見性,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-05-05
  • Rust在Android端集成使用詳解

    Rust在Android端集成使用詳解

    本文介紹了如何在Android平臺(tái)上調(diào)用Rust編寫的組件,詳細(xì)說明了開發(fā)環(huán)境的搭建、Rust庫的創(chuàng)建、配置和編譯過程,以及如何在Android應(yīng)用中使用Rust編寫的代碼,文中提到飛書底層使用Rust編寫通用組件,展示了Rust在移動(dòng)端開發(fā)中的應(yīng)用價(jià)值
    2024-11-11
  • Rust中使用Serde對json數(shù)據(jù)進(jìn)行反序列化

    Rust中使用Serde對json數(shù)據(jù)進(jìn)行反序列化

    JSON作為目前流行的數(shù)據(jù)格式之一,被大家廣泛使用,在日常的開發(fā)實(shí)踐中,將JSON數(shù)據(jù)反序列化為對應(yīng)的類型具有重要的意義,在Rust中,Serde幾乎成了JSON數(shù)據(jù)解析的事實(shí)標(biāo)準(zhǔn),本文將給大家介紹Rust中使用Serde對json數(shù)據(jù)進(jìn)行反序列化,需要的朋友可以參考下
    2024-01-01
  • Rust 能夠取代 C 語言嗎

    Rust 能夠取代 C 語言嗎

    Rust 是 Mozilla 基金會(huì)的一個(gè)雄心勃勃的項(xiàng)目,號稱是 C 語言和 C++ 的繼任者,這篇文章主要介紹了Rust 能夠取代 C 語言嗎的相關(guān)知識(shí),需要的朋友可以參考下
    2020-06-06
  • 使用Rust實(shí)現(xiàn)日志記錄功能

    使用Rust實(shí)現(xiàn)日志記錄功能

    這篇文章主要為大家詳細(xì)介紹了使用Rust實(shí)現(xiàn)日志記錄功能的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的可以參考一下
    2024-04-04
  • Rust實(shí)現(xiàn)AES加解密詳解

    Rust實(shí)現(xiàn)AES加解密詳解

    這篇文章主要為大家詳細(xì)介紹了如何利用Rust語言實(shí)現(xiàn)AES加解密算法,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以了解一下
    2022-10-10
  • Rust中箱、包和模塊的學(xué)習(xí)筆記

    Rust中箱、包和模塊的學(xué)習(xí)筆記

    Rust中有三個(gè)重要的組織概念:箱、包、模塊,本文主要介紹了Rust中箱、包和模塊的學(xué)習(xí)筆記,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-03-03
  • rust中的match表達(dá)式使用詳解

    rust中的match表達(dá)式使用詳解

    在rust中提供了一個(gè)極為強(qiáng)大的控制流運(yùn)算符match,這篇文章主要介紹了rust中的match表達(dá)式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • Rust控制流運(yùn)算符match的用法詳解

    Rust控制流運(yùn)算符match的用法詳解

    match 是Rust中一個(gè)極為強(qiáng)大的控制流運(yùn)算符,用于模式匹配和控制流的選擇,它允許將一個(gè)值與一系列的模式相比較,根據(jù)匹配的模式執(zhí)行相應(yīng)代碼,本文給大家詳細(xì)介紹了Rust控制流運(yùn)算符match的用法,需要的朋友可以參考下
    2024-01-01

最新評論