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 ®ion,
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 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 #[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}