qiniu / rust-sdk

Generic Qiniu Resource Storage SDK
MIT License
26 stars 7 forks source link

在actix-web中的使用实例 #2

Closed januwA closed 2 years ago

januwA commented 2 years ago

你好,我正在 actix-web4 中将图片上传到七牛云,但是我不知道怎么将字节发送到七牛云,能帮帮我吗。

这是我的代码

use qiniu_sdk::upload::{
    apis::credential::Credential, AutoUploader, AutoUploaderObjectParams, UploadManager,
    UploadTokenSigner,
};
use std::time::Duration;

#[post("upload_qn")]
pub async fn upload_qn(mut payload: Multipart) -> HttpResult {
    let access_key = "-";
    let secret_key = "-";
    let bucket_name = "-";

    while let Some(mut field) = payload.try_next().await? {
        let field_name = field.name();
        if field_name == "file" {
            let content_type = field.content_type();
            if content_type.type_() != mime::IMAGE {
                continue;
            }
            let content_disposition = field.content_disposition();
            let filename = content_disposition
                .get_filename()
                .map_or_else(|| Uuid::new_v4().to_string(), sanitize_filename::sanitize);

            let filename: &str =
                std::path::PathBuf::from(format!("{}_{}", &Uuid::new_v4(), &filename))
                    .to_str()
                    .unwrap();

            let credential = Credential::new(access_key, secret_key);
            let upload_manager =
                UploadManager::builder(UploadTokenSigner::new_credential_provider(
                    credential,
                    bucket_name,
                    Duration::from_secs(3600),
                ))
                .build();
            let mut uploader: AutoUploader = upload_manager.auto_uploader();
            let params = AutoUploaderObjectParams::builder()
                // .object_name(object_name)
                .file_name(filename)
                .build();

            while let Some(chunk) = field.try_next().await? {
                uploader.async_upload_reader(&chunk, params); // 这里会报错,我该怎样发送这个 field 到七牛云
            }

            return res_ok!("ok");
        }
    }

    Err(actix_web::Error::from(OkError::es("上传失败")))
}
qiniu-sdk = { version = "0.1.0", features = ["utils", "etag", "credential", "upload-token", "http", "http-client", "apis", "objects", "upload", "async", "reqwest"] }
bachue commented 2 years ago

@januwA 你好,请提供具体的错误信息。

我看了下 actix-web 的文档,估计这里的 chunk 类型是 bytes::Bytes 类型,如果是这样的话,可以调用 futures::io::Cursor 为其实现 AsyncRead,这样就可以提供给 async_upload_reader 方法了,然后注意这个方法是 async 的所以要跟 await

januwA commented 2 years ago

@bachue 错误信息正如你说的那样,我不清楚怎么将bytes传递给async_upload_reader

我现在将文件保存在本地,然后使用 async_upload_path上传后在删除本地图片

#[post("upload_qn")]
pub async fn upload_qn(mut payload: Multipart) -> HttpResult {
    let access_key = "-";
    let secret_key = "-";
    let bucket_name = "-";
    let save_dir = "./public/qn/";

    while let Some(mut field) = payload.try_next().await? {
        let field_name = field.name();
        if field_name == "file" {
            let content_disposition = field.content_disposition();
            let filename = content_disposition
                .get_filename()
                .map_or_else(|| Uuid::new_v4().to_string(), sanitize_filename::sanitize);

            let filename = format!("{}_{}", Uuid::new_v4().to_string(), &filename);
            let filepath = format!("{}{}", save_dir, &filename);

            tokio::fs::create_dir_all(&save_dir).await?;
            let mut f = tokio::fs::File::create(&filepath).await?;

            while let Some(chunk) = field.try_next().await? {
                f.write_all(&chunk).await?;
            }

            let credential = Credential::new(access_key, secret_key);
            let upload_manager =
                UploadManager::builder(UploadTokenSigner::new_credential_provider(
                    credential,
                    bucket_name,
                    Duration::from_secs(3600),
                ))
                .build();

            let uploader: AutoUploader = upload_manager.auto_uploader();
            let params = AutoUploaderObjectParams::builder()
                .object_name(&filename)
                .file_name(&filename)
                .build();

            uploader
                .async_upload_path(&filepath, params)
                .await
                .map_err(|e| OkError::e_s(e.to_string()))?;

            // 上传后删除文件
            tokio::fs::remove_file(&filepath).await?;

            return res_ok!("");
        }
    }

    Err(OkError::actix("上传失败"))
}
bachue commented 2 years ago

@januwA 看了下,有点麻烦,本来 Field 可以转变成 AsyncRead 的,但是偏偏 Field 内部用的是 Rc 导致它既不是 Sync 也不是 Send 的,导致就没法送进 async_upload_reader 了,我们这边因为内部实现的原因,强制要求 Reader 要实现 AsyncRead + Unpin + Debug + Send + Sync + 'static

bachue commented 2 years ago

@januwA 你这边用的 Multipart 库能替换成其他的吗?

januwA commented 2 years ago

@bachue 你好,现在我使用的是actix_multipart,我还不清楚其它Multipart 库怎么在actix-web中使用

bachue commented 2 years ago

@januwA 一下子也想不到什么更好的方法,actix-web 好像也官方支持 actix_multipart。目前可能可以的方式是用 tokio 的 tokio::sync::mpsc::channel 库,让 actix_multipartSender 发数据,然后将 Receiver 封装成 AsyncRead 的实现。

januwA commented 2 years ago

@bachue 嗯