Skip to main content

sz_orm_storage/
storage.rs

1use crate::aliyun::AliyunOssStorage;
2use crate::error::StorageError;
3use crate::huawei::HuaweiObsStorage;
4use crate::local::LocalStorage;
5use crate::qiniu::QiniuKodoStorage;
6#[cfg(feature = "real-cloud")]
7use crate::real::RealCloudStorage;
8use crate::s3::S3Storage;
9use crate::tencent::TencentCosStorage;
10use crate::upyun::UpYunStorage;
11use async_trait::async_trait;
12
13#[async_trait]
14pub trait Storage: Send + Sync {
15    async fn put(&self, key: &str, data: &[u8], content_type: &str)
16        -> Result<String, StorageError>;
17    async fn get(&self, key: &str) -> Result<Vec<u8>, StorageError>;
18    async fn delete(&self, key: &str) -> Result<(), StorageError>;
19    async fn exists(&self, key: &str) -> Result<bool, StorageError>;
20}
21
22pub struct StorageBuilder {
23    provider: StorageProvider,
24    config: StorageConfig,
25}
26
27impl StorageBuilder {
28    pub fn new(provider: StorageProvider) -> Self {
29        Self {
30            provider,
31            config: StorageConfig::default(),
32        }
33    }
34
35    pub fn with_bucket(mut self, bucket: impl Into<String>) -> Self {
36        self.config.bucket = bucket.into();
37        self
38    }
39
40    pub fn with_region(mut self, region: impl Into<String>) -> Self {
41        self.config.region = region.into();
42        self
43    }
44
45    pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
46        self.config.endpoint = Some(endpoint.into());
47        self
48    }
49
50    pub fn with_access_key(mut self, key: impl Into<String>) -> Self {
51        self.config.access_key = Some(key.into());
52        self
53    }
54
55    pub fn with_secret_key(mut self, key: impl Into<String>) -> Self {
56        self.config.secret_key = Some(key.into());
57        self
58    }
59
60    pub fn with_path_prefix(mut self, prefix: impl Into<String>) -> Self {
61        self.config.path_prefix = Some(prefix.into());
62        self
63    }
64
65    pub fn with_base_path(mut self, base_path: impl Into<String>) -> Self {
66        self.config.base_path = Some(base_path.into());
67        self
68    }
69
70    pub fn build(self) -> Result<StorageWrapper, StorageError> {
71        match self.provider {
72            StorageProvider::Local => {
73                let base_path = self
74                    .config
75                    .base_path
76                    .clone()
77                    .unwrap_or_else(|| ".".to_string());
78                Ok(StorageWrapper::Local(LocalStorage::new(base_path)))
79            }
80            StorageProvider::S3(_) => {
81                let bucket = self.config.bucket.clone();
82                let region = self.config.region.clone();
83                Ok(StorageWrapper::S3(S3Storage::new(bucket, region)))
84            }
85            StorageProvider::AliyunOss(_) => {
86                #[cfg(feature = "real-cloud")]
87                {
88                    let bucket = self.config.bucket.clone();
89                    let endpoint = self.config.endpoint.clone().unwrap_or_default();
90                    let access_key = self.config.access_key.clone().ok_or_else(|| {
91                        StorageError::InvalidConfig(
92                            "aliyun oss: 需要 with_access_key 配置 AccessKeyId".into(),
93                        )
94                    })?;
95                    let secret_key = self.config.secret_key.clone().ok_or_else(|| {
96                        StorageError::InvalidConfig(
97                            "aliyun oss: 需要 with_secret_key 配置 AccessKeySecret".into(),
98                        )
99                    })?;
100                    let real =
101                        crate::real::aliyun_oss(&bucket, &endpoint, &access_key, &secret_key)?;
102                    Ok(StorageWrapper::Cloud(RealCloudStorage::Aliyun(real)))
103                }
104                #[cfg(not(feature = "real-cloud"))]
105                {
106                    let bucket = self.config.bucket.clone();
107                    let endpoint = self.config.endpoint.clone().unwrap_or_default();
108                    Ok(StorageWrapper::Aliyun(AliyunOssStorage::new(
109                        bucket, endpoint,
110                    )))
111                }
112            }
113            StorageProvider::TencentCos(tencent_cfg) => {
114                #[cfg(feature = "real-cloud")]
115                {
116                    let bucket = self.config.bucket.clone();
117                    let region = self.config.region.clone();
118                    let endpoint = self.config.endpoint.clone();
119                    let secret_id = tencent_cfg
120                        .secret_id
121                        .or_else(|| self.config.access_key.clone())
122                        .ok_or_else(|| {
123                            StorageError::InvalidConfig(
124                                "tencent cos: 需要 with_access_key 配置 SecretId".into(),
125                            )
126                        })?;
127                    let secret_key = tencent_cfg
128                        .secret_key
129                        .or_else(|| self.config.secret_key.clone())
130                        .ok_or_else(|| {
131                            StorageError::InvalidConfig(
132                                "tencent cos: 需要 with_secret_key 配置 SecretKey".into(),
133                            )
134                        })?;
135                    let real = crate::real::tencent_cos(
136                        &bucket,
137                        &region,
138                        endpoint,
139                        &secret_id,
140                        &secret_key,
141                    )?;
142                    Ok(StorageWrapper::Cloud(RealCloudStorage::Tencent(real)))
143                }
144                #[cfg(not(feature = "real-cloud"))]
145                {
146                    let _ = &tencent_cfg;
147                    let bucket = self.config.bucket.clone();
148                    let region = self.config.region.clone();
149                    Ok(StorageWrapper::Tencent(TencentCosStorage::new(
150                        bucket, region,
151                    )))
152                }
153            }
154            StorageProvider::QiniuKodo(_) => {
155                #[cfg(feature = "real-cloud")]
156                {
157                    let bucket = self.config.bucket.clone();
158                    let access_key = self.config.access_key.clone().ok_or_else(|| {
159                        StorageError::InvalidConfig(
160                            "qiniu kodo: 需要 with_access_key 配置 AccessKey".into(),
161                        )
162                    })?;
163                    let secret_key = self.config.secret_key.clone().ok_or_else(|| {
164                        StorageError::InvalidConfig(
165                            "qiniu kodo: 需要 with_secret_key 配置 SecretKey".into(),
166                        )
167                    })?;
168                    // endpoint 作为下载域名(私有桶下载需域名);缺省用 {bucket}.qiniudn.com
169                    let domain = self
170                        .config
171                        .endpoint
172                        .clone()
173                        .unwrap_or_else(|| format!("{}.qiniudn.com", bucket));
174                    let real = crate::real::RealQiniuKodoStorage::new(
175                        bucket, access_key, secret_key, domain,
176                    );
177                    Ok(StorageWrapper::Cloud(RealCloudStorage::Qiniu(real)))
178                }
179                #[cfg(not(feature = "real-cloud"))]
180                {
181                    let bucket = self.config.bucket.clone();
182                    Ok(StorageWrapper::Qiniu(QiniuKodoStorage::new(bucket)))
183                }
184            }
185            StorageProvider::HuaweiObs(_) => {
186                #[cfg(feature = "real-cloud")]
187                {
188                    let bucket = self.config.bucket.clone();
189                    let endpoint = self.config.endpoint.clone().unwrap_or_default();
190                    let access_key = self.config.access_key.clone().ok_or_else(|| {
191                        StorageError::InvalidConfig(
192                            "huawei obs: 需要 with_access_key 配置 AccessKeyId".into(),
193                        )
194                    })?;
195                    let secret_key = self.config.secret_key.clone().ok_or_else(|| {
196                        StorageError::InvalidConfig(
197                            "huawei obs: 需要 with_secret_key 配置 SecretAccessKey".into(),
198                        )
199                    })?;
200                    let real =
201                        crate::real::huawei_obs(&bucket, &endpoint, &access_key, &secret_key)?;
202                    Ok(StorageWrapper::Cloud(RealCloudStorage::Huawei(real)))
203                }
204                #[cfg(not(feature = "real-cloud"))]
205                {
206                    let bucket = self.config.bucket.clone();
207                    let endpoint = self.config.endpoint.clone().unwrap_or_default();
208                    Ok(StorageWrapper::Huawei(HuaweiObsStorage::new(
209                        bucket, endpoint,
210                    )))
211                }
212            }
213            StorageProvider::UpYun(upyun_cfg) => {
214                #[cfg(feature = "real-cloud")]
215                {
216                    let bucket = self.config.bucket.clone();
217                    let operator = upyun_cfg
218                        .operator
219                        .or_else(|| self.config.access_key.clone())
220                        .ok_or_else(|| {
221                            StorageError::InvalidConfig(
222                                "upyun: 需要 with_access_key 配置操作员名 operator".into(),
223                            )
224                        })?;
225                    let password = upyun_cfg
226                        .password
227                        .or_else(|| self.config.secret_key.clone())
228                        .ok_or_else(|| {
229                            StorageError::InvalidConfig(
230                                "upyun: 需要 with_secret_key 配置操作员密码 password".into(),
231                            )
232                        })?;
233                    let real = crate::real::upyun(&bucket, &operator, &password)?;
234                    Ok(StorageWrapper::Cloud(RealCloudStorage::Upyun(real)))
235                }
236                #[cfg(not(feature = "real-cloud"))]
237                {
238                    let _ = &upyun_cfg;
239                    let bucket = self.config.bucket.clone();
240                    Ok(StorageWrapper::Upyun(UpYunStorage::new(bucket)))
241                }
242            }
243        }
244    }
245}
246
247#[derive(Clone)]
248pub struct StorageConfig {
249    pub bucket: String,
250    pub region: String,
251    pub endpoint: Option<String>,
252    pub access_key: Option<String>,
253    pub secret_key: Option<String>,
254    pub path_prefix: Option<String>,
255    pub base_path: Option<String>,
256}
257
258impl std::fmt::Debug for StorageConfig {
259    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260        f.debug_struct("StorageConfig")
261            .field("bucket", &self.bucket)
262            .field("region", &self.region)
263            .field("endpoint", &self.endpoint)
264            .field("access_key", &"***")
265            .field("secret_key", &"***")
266            .field("path_prefix", &self.path_prefix)
267            .field("base_path", &self.base_path)
268            .finish()
269    }
270}
271
272impl Default for StorageConfig {
273    fn default() -> Self {
274        Self {
275            bucket: "default-bucket".to_string(),
276            region: "us-east-1".to_string(),
277            endpoint: None,
278            access_key: None,
279            secret_key: None,
280            path_prefix: None,
281            base_path: None,
282        }
283    }
284}
285
286#[derive(Debug, Clone)]
287pub enum StorageProvider {
288    Local,
289    S3(S3Config),
290    AliyunOss(AliyunConfig),
291    TencentCos(TencentConfig),
292    QiniuKodo(QiniuConfig),
293    HuaweiObs(HuaweiConfig),
294    UpYun(UpYunConfig),
295}
296
297#[derive(Debug, Clone, Default)]
298pub struct S3Config {
299    pub region: String,
300    pub access_key: Option<String>,
301    pub secret_key: Option<String>,
302}
303
304#[derive(Debug, Clone, Default)]
305pub struct AliyunConfig {
306    pub endpoint: String,
307    pub access_key: Option<String>,
308    pub secret_key: Option<String>,
309}
310
311#[derive(Debug, Clone, Default)]
312pub struct TencentConfig {
313    pub region: String,
314    pub secret_id: Option<String>,
315    pub secret_key: Option<String>,
316}
317
318#[derive(Debug, Clone, Default)]
319pub struct QiniuConfig {
320    pub access_key: Option<String>,
321    pub secret_key: Option<String>,
322}
323
324#[derive(Debug, Clone, Default)]
325pub struct HuaweiConfig {
326    pub endpoint: String,
327    pub access_key: Option<String>,
328    pub secret_key: Option<String>,
329}
330
331#[derive(Debug, Clone, Default)]
332pub struct UpYunConfig {
333    pub operator: Option<String>,
334    pub password: Option<String>,
335}
336
337pub enum StorageWrapper {
338    Local(LocalStorage),
339    S3(S3Storage),
340    Aliyun(AliyunOssStorage),
341    Tencent(TencentCosStorage),
342    Qiniu(QiniuKodoStorage),
343    Huawei(HuaweiObsStorage),
344    Upyun(UpYunStorage),
345    /// 真实云存储(feature = "real-cloud"):OSS / COS / OBS / UpYun / Qiniu
346    #[cfg(feature = "real-cloud")]
347    Cloud(RealCloudStorage),
348}
349
350#[async_trait]
351impl Storage for StorageWrapper {
352    async fn put(
353        &self,
354        key: &str,
355        data: &[u8],
356        content_type: &str,
357    ) -> Result<String, StorageError> {
358        match self {
359            StorageWrapper::Local(s) => s.put(key, data, content_type).await,
360            StorageWrapper::S3(s) => s.put(key, data, content_type).await,
361            StorageWrapper::Aliyun(s) => s.put(key, data, content_type).await,
362            StorageWrapper::Tencent(s) => s.put(key, data, content_type).await,
363            StorageWrapper::Qiniu(s) => s.put(key, data, content_type).await,
364            StorageWrapper::Huawei(s) => s.put(key, data, content_type).await,
365            StorageWrapper::Upyun(s) => s.put(key, data, content_type).await,
366            #[cfg(feature = "real-cloud")]
367            StorageWrapper::Cloud(s) => s.put(key, data, content_type).await,
368        }
369    }
370
371    async fn get(&self, key: &str) -> Result<Vec<u8>, StorageError> {
372        match self {
373            StorageWrapper::Local(s) => s.get(key).await,
374            StorageWrapper::S3(s) => s.get(key).await,
375            StorageWrapper::Aliyun(s) => s.get(key).await,
376            StorageWrapper::Tencent(s) => s.get(key).await,
377            StorageWrapper::Qiniu(s) => s.get(key).await,
378            StorageWrapper::Huawei(s) => s.get(key).await,
379            StorageWrapper::Upyun(s) => s.get(key).await,
380            #[cfg(feature = "real-cloud")]
381            StorageWrapper::Cloud(s) => s.get(key).await,
382        }
383    }
384
385    async fn delete(&self, key: &str) -> Result<(), StorageError> {
386        match self {
387            StorageWrapper::Local(s) => s.delete(key).await,
388            StorageWrapper::S3(s) => s.delete(key).await,
389            StorageWrapper::Aliyun(s) => s.delete(key).await,
390            StorageWrapper::Tencent(s) => s.delete(key).await,
391            StorageWrapper::Qiniu(s) => s.delete(key).await,
392            StorageWrapper::Huawei(s) => s.delete(key).await,
393            StorageWrapper::Upyun(s) => s.delete(key).await,
394            #[cfg(feature = "real-cloud")]
395            StorageWrapper::Cloud(s) => s.delete(key).await,
396        }
397    }
398
399    async fn exists(&self, key: &str) -> Result<bool, StorageError> {
400        match self {
401            StorageWrapper::Local(s) => s.exists(key).await,
402            StorageWrapper::S3(s) => s.exists(key).await,
403            StorageWrapper::Aliyun(s) => s.exists(key).await,
404            StorageWrapper::Tencent(s) => s.exists(key).await,
405            StorageWrapper::Qiniu(s) => s.exists(key).await,
406            StorageWrapper::Huawei(s) => s.exists(key).await,
407            StorageWrapper::Upyun(s) => s.exists(key).await,
408            #[cfg(feature = "real-cloud")]
409            StorageWrapper::Cloud(s) => s.exists(key).await,
410        }
411    }
412}