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#[derive(Debug, Clone)]
20pub struct QiniuFile {
21 pub domain: String,
23 pub desk: String,
25 pub mine: String,
27 pub name: String,
29 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 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 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 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 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 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 let res = uploader.async_upload_path(&temp, params).await;
151
152 tokio::fs::remove_file(&temp)
154 .await
155 .map_err(|_| anyhow!("删除本地缓存文件失败"))?;
156
157 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}