spring_qiniu/
upload.rs

1use crate::config::QiniuConfig;
2use anyhow::{anyhow, Result};
3use bytes::Bytes;
4use qiniu_sdk::apis::credential::Credential;
5pub use qiniu_sdk::upload::{AutoUploader, AutoUploaderObjectParams, UploadManager, UploadTokenSigner};
6use std::fmt::Debug;
7use std::path::Path;
8use std::time::Duration;
9use tokio::fs::metadata;
10
11#[derive(Debug, Clone)]
12pub struct QiniuUpload {
13    pub config: QiniuConfig,
14    pub upload_manager: UploadManager,
15}
16
17/// # 构建文件对象
18/// - 文件路径: {desk}/{mine}/{name} 示例:129382362732/image/3j28d3282-82h93h23-78hd8237-h2d83d32.jpg
19#[derive(Debug, Clone)]
20pub struct QiniuFile {
21    // 域名
22    pub domain: String,
23    // 存储空间 - store10001
24    pub desk: String,
25    // 文件类型:image / video / audio / text
26    pub mine: String,
27    // 文件名称: 123456.jpg
28    pub name: String,
29    // hash
30    pub hash: String,
31}
32
33impl QiniuFile {
34    pub fn url(&self) -> String {
35        format!("{}/{}/{}/{}", self.domain, self.desk, self.mine, self.name)
36    }
37}
38
39impl QiniuUpload {
40    pub fn new(config: QiniuConfig) -> Self {
41        let upload_manager = UploadManager::builder(UploadTokenSigner::new_credential_provider(
42            Credential::new(&config.access_key, &config.secret_key),
43            &config.bucket_name,
44            Duration::from_secs(3600),
45        )).build();
46        QiniuUpload { config, upload_manager }
47    }
48    pub fn re_build(&mut self) {
49        self.upload_manager = UploadManager::builder(UploadTokenSigner::new_credential_provider(
50            Credential::new(&self.config.access_key, &self.config.secret_key),
51            &self.config.bucket_name,
52            Duration::from_secs(3600),
53        )).build();
54    }
55    pub async fn upload_image(&self, disk: &str, file: &str) -> Result<QiniuFile> {
56        let (ext, size) = &self.get_file_info(file).await?;
57        if *size > self.config.image_size {
58            return Err(anyhow!("图片大小超出限制: {}", self.config.image_size));
59        }
60        // 验证文件类型
61        if !self.config.image_ext.contains(&ext.to_string()) {
62            return Err(anyhow!("图片格式仅允许: {}", self.config.image_ext.join("、")).into());
63        }
64        self.upload_file(disk, "image", &ext, file).await
65    }
66    pub async fn upload_image_bytes(&self, disk: &str, ext: &str, bytes: &Bytes) -> Result<QiniuFile> {
67        let size = bytes.len() as u64;
68        if size > self.config.image_size {
69            return Err(anyhow!("图片大小超出限制: {}", self.config.image_size).into());
70        }
71        // 验证文件类型
72        if !self.config.image_ext.contains(&ext.to_string()) {
73            return Err(anyhow!("图片格式仅允许: {}", self.config.image_ext.join("、")).into());
74        }
75        self.upload_render(disk, "image", &ext, bytes).await
76    }
77    pub async fn upload_video(&self, disk: &str, file: &str) -> Result<QiniuFile> {
78        let (ext, size) = &self.get_file_info(file).await?;
79        if *size > self.config.video_size {
80            return Err(anyhow!("视频大小超出限制: {}", self.config.video_size).into());
81        }
82        // 验证文件类型
83        if !self.config.video_ext.contains(&ext.to_string()) {
84            return Err(anyhow!("视频格式仅允许: {}", self.config.video_ext.join("、")).into());
85        }
86        self.upload_file(disk, "video", &ext, file).await
87    }
88    pub async fn upload_video_bytes(&self, disk: &str, ext: &str, bytes: &Bytes) -> Result<QiniuFile> {
89        let size = bytes.len() as u64;
90        if size > self.config.video_size {
91            return Err(anyhow!("视频大小超出限制: {}", self.config.video_size).into());
92        }
93        // 验证文件类型
94        if !self.config.video_ext.contains(&ext.to_string()) {
95            return Err(anyhow!("视频格式仅允许: {}", self.config.video_ext.join("、")).into());
96        }
97        self.upload_render(disk, "video", &ext, bytes).await
98    }
99    async fn upload_file(&self, disk: &str, mine: &str, ext: &str, file: &str) -> Result<QiniuFile> {
100        if !disk.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
101            return Err(anyhow!("无效的 disk 参数"));
102        }
103        if !mine.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
104            return Err(anyhow!("无效的 mine 参数"));
105        }
106        let name = format!("{}.{}", uuid::Uuid::new_v4(), ext).replace("-", "").to_lowercase();
107        let object_name = &format!("{}/{}/{}", disk, mine, name);
108        let params = AutoUploaderObjectParams::builder().object_name(object_name).build();
109        let uploader: AutoUploader = self.upload_manager.auto_uploader();
110        uploader.async_upload_path(file, params).await.and_then(|e| {
111            Ok(QiniuFile {
112                domain: self.config.domain.clone(),
113                desk: disk.to_string(),
114                mine: mine.to_string(),
115                name,
116                hash: e.get("hash").unwrap().as_str().unwrap().to_string(),
117            })
118        }).map_err(|e| {
119            anyhow!("上传失败: {}", e.to_string())
120        })
121    }
122    async fn upload_render(&self, disk: &str, mine: &str, ext: &str, bytes: &Bytes) -> Result<QiniuFile> {
123        if !disk.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
124            return Err(anyhow!("无效的 disk 参数"));
125        }
126        if !mine.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
127            return Err(anyhow!("无效的 mine 参数"));
128        }
129        if bytes.len() == 0 {
130            return Err(anyhow!("上传的数据不能为空"));
131        }
132        let name = format!("{}.{}", uuid::Uuid::new_v4(), ext).replace("-", "").to_lowercase();
133        let object_name = format!("{}/{}/{}", disk, mine, name);
134        let params = AutoUploaderObjectParams::builder().object_name(&object_name).build();
135        let uploader: AutoUploader = self.upload_manager.auto_uploader();
136        let temp = format!("tmp/{}", &object_name);
137
138        // 先存储到临时目录,再上传到云端
139        let path = Path::new(&temp);
140        if let Some(parent) = path.parent() {
141            tokio::fs::create_dir_all(parent)
142                .await
143                .map_err(|err| anyhow!("临时目录创建失败: {}", err))?;
144        }
145        tokio::fs::write(&temp, bytes)
146            .await
147            .map_err(|_| anyhow!("上传文件缓存失败"))?;
148
149        // 上传到云端
150        let res = uploader.async_upload_path(&temp, params).await;
151
152        // 删除本地缓存
153        tokio::fs::remove_file(&temp)
154            .await
155            .map_err(|_| anyhow!("删除本地缓存文件失败"))?;
156
157        // 返回结果
158        res.and_then(|e| {
159            Ok(QiniuFile {
160                domain: self.config.domain.clone(),
161                desk: disk.to_string(),
162                mine: mine.to_string(),
163                name,
164                hash: e.get("hash").unwrap().as_str().unwrap().to_string(),
165            })
166        }).map_err(|e| {
167            anyhow!("上传失败: {}", e.to_string())
168        })
169    }
170    async fn get_file_info(&self, file: &str) -> Result<(String, u64)> {
171        let path = Path::new(file);
172        if !path.is_file() {
173            return Err(anyhow!("文件不存在"));
174        }
175        let ext = path.extension().and_then(|e| e.to_str());
176        if ext.is_none() {
177            return Err(anyhow!("文件后缀名不存在"));
178        }
179        let metadata = metadata(path).await.map_err(|_| {
180            anyhow!("文件类型错误")
181        })?;
182        let file_size = metadata.len();
183        Ok((ext.unwrap().to_string(), file_size))
184    }
185}