Skip to main content

sz_orm_storage/
advanced.rs

1//! # 高级存储功能
2//!
3//! 提供分片上传(Multipart Upload)、断点续传、存储桶生命周期管理、
4//! CDN 刷新等高级对象存储能力。所有实现均为纯内存模型,可在不依赖真实云服务的情况下进行单元测试。
5
6use crate::error::StorageError;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::Mutex;
11use std::time::{Duration, SystemTime, UNIX_EPOCH};
12
13/// 全局上传 ID 计数器,确保即使在同一纳秒内发起的多次上传也有唯一 ID
14static UPLOAD_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
15
16/// 全局刷新请求 ID 计数器
17static REFRESH_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
18
19// ====================================================================
20// 分片上传(Multipart Upload)
21// ====================================================================
22
23/// 单个分片的状态
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct Part {
26    /// 分片编号(从 1 开始)
27    pub number: u32,
28    /// 分片数据
29    pub data: Vec<u8>,
30    /// 分片大小(字节)
31    pub size: usize,
32    /// 分片 ETag(简单实现用分片编号的哈希)
33    pub etag: String,
34}
35
36impl Part {
37    pub fn new(number: u32, data: Vec<u8>) -> Self {
38        let size = data.len();
39        let etag = format!("etag-{:x}-{:x}", number, size);
40        Self {
41            number,
42            data,
43            size,
44            etag,
45        }
46    }
47}
48
49/// 分片上传状态
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51pub enum UploadStatus {
52    /// 已初始化,等待上传分片
53    Initiated,
54    /// 正在上传分片
55    InProgress,
56    /// 已完成
57    Completed,
58    /// 已中止
59    Aborted,
60}
61
62/// 分片上传会话
63#[derive(Debug, Serialize, Deserialize)]
64pub struct MultipartUpload {
65    /// 上传 ID
66    pub upload_id: String,
67    /// 目标对象 key
68    pub key: String,
69    /// Content-Type
70    pub content_type: String,
71    /// 已上传的分片列表
72    pub parts: Vec<Part>,
73    /// 预期的总分片数
74    pub expected_parts: u32,
75    /// 每个分片的大小阈值(字节)
76    pub part_size: usize,
77    /// 上传状态
78    pub status: UploadStatus,
79    /// 创建时间戳(秒)
80    pub created_at: u64,
81}
82
83impl MultipartUpload {
84    /// 创建新的分片上传会话
85    pub fn new(key: impl Into<String>, content_type: impl Into<String>, part_size: usize) -> Self {
86        let now = SystemTime::now()
87            .duration_since(UNIX_EPOCH)
88            .unwrap_or_default()
89            .as_secs();
90        let counter = UPLOAD_ID_COUNTER.fetch_add(1, Ordering::SeqCst);
91        Self {
92            upload_id: format!("upload-{}-{}", now, counter),
93            key: key.into(),
94            content_type: content_type.into(),
95            parts: Vec::new(),
96            expected_parts: 0,
97            part_size,
98            status: UploadStatus::Initiated,
99            created_at: now,
100        }
101    }
102
103    /// 上传一个分片
104    pub fn upload_part(&mut self, number: u32, data: Vec<u8>) -> Result<String, StorageError> {
105        if self.status == UploadStatus::Completed || self.status == UploadStatus::Aborted {
106            return Err(StorageError::Put(format!(
107                "upload {} already {:?}",
108                self.upload_id, self.status
109            )));
110        }
111        // 分片编号必须连续且递增
112        let next_number = self.parts.last().map(|p| p.number + 1).unwrap_or(1);
113        if number != next_number {
114            return Err(StorageError::Put(format!(
115                "expected part {}, got {}",
116                next_number, number
117            )));
118        }
119        // 分片大小检查(除最后一个分片外必须等于 part_size)
120        if number < self.expected_parts && data.len() != self.part_size {
121            return Err(StorageError::Put(format!(
122                "part {} size {} != expected {}",
123                number,
124                data.len(),
125                self.part_size
126            )));
127        }
128        let part = Part::new(number, data);
129        let etag = part.etag.clone();
130        self.parts.push(part);
131        self.status = UploadStatus::InProgress;
132        Ok(etag)
133    }
134
135    /// 完成分片上传,返回合并后的完整数据
136    pub fn complete(&mut self) -> Result<Vec<u8>, StorageError> {
137        if self.status == UploadStatus::Completed {
138            return Err(StorageError::Put(format!(
139                "upload {} already completed",
140                self.upload_id
141            )));
142        }
143        if self.status == UploadStatus::Aborted {
144            return Err(StorageError::Put(format!(
145                "upload {} was aborted",
146                self.upload_id
147            )));
148        }
149        if self.parts.is_empty() {
150            return Err(StorageError::Put(format!(
151                "upload {} has no parts",
152                self.upload_id
153            )));
154        }
155        // 验证分片编号连续
156        for (idx, part) in self.parts.iter().enumerate() {
157            if part.number as usize != idx + 1 {
158                return Err(StorageError::Put(format!(
159                    "part numbering gap: expected {}, got {}",
160                    idx + 1,
161                    part.number
162                )));
163            }
164        }
165        let mut combined = Vec::new();
166        for part in &self.parts {
167            combined.extend_from_slice(&part.data);
168        }
169        self.status = UploadStatus::Completed;
170        Ok(combined)
171    }
172
173    /// 中止分片上传
174    pub fn abort(&mut self) -> Result<(), StorageError> {
175        if self.status == UploadStatus::Completed {
176            return Err(StorageError::Put(format!(
177                "upload {} already completed, cannot abort",
178                self.upload_id
179            )));
180        }
181        self.status = UploadStatus::Aborted;
182        self.parts.clear();
183        Ok(())
184    }
185
186    /// 返回已上传分片数量
187    pub fn uploaded_part_count(&self) -> usize {
188        self.parts.len()
189    }
190
191    /// 返回已上传字节数
192    pub fn uploaded_bytes(&self) -> usize {
193        self.parts.iter().map(|p| p.size).sum()
194    }
195
196    /// 返回上传进度百分比(0-100)
197    pub fn progress_percent(&self) -> u8 {
198        if self.expected_parts == 0 {
199            return if self.status == UploadStatus::Completed {
200                100
201            } else {
202                0
203            };
204        }
205        ((self.parts.len() as f64 / self.expected_parts as f64) * 100.0) as u8
206    }
207
208    /// 判断上传是否已完成
209    pub fn is_completed(&self) -> bool {
210        self.status == UploadStatus::Completed
211    }
212}
213
214// ====================================================================
215// 断点续传(Resumable Upload)
216// ====================================================================
217
218/// 断点续传管理器:持久化分片上传状态,支持中断后恢复
219pub struct ResumableUploadManager {
220    /// 所有活跃的上传会话(upload_id -> MultipartUpload)
221    sessions: Mutex<HashMap<String, MultipartUpload>>,
222    /// 已完成上传的合并数据缓存(key -> data)
223    completed: Mutex<HashMap<String, Vec<u8>>>,
224}
225
226impl ResumableUploadManager {
227    pub fn new() -> Self {
228        Self {
229            sessions: Mutex::new(HashMap::new()),
230            completed: Mutex::new(HashMap::new()),
231        }
232    }
233
234    /// 发起分片上传
235    pub fn initiate(
236        &self,
237        key: &str,
238        content_type: &str,
239        total_size: usize,
240        part_size: usize,
241    ) -> Result<String, StorageError> {
242        let mut upload = MultipartUpload::new(key, content_type, part_size);
243        upload.expected_parts = total_size.div_ceil(part_size) as u32;
244        let upload_id = upload.upload_id.clone();
245        let mut sessions = self
246            .sessions
247            .lock()
248            .map_err(|e| StorageError::Connection(format!("lock error: {}", e)))?;
249        sessions.insert(upload_id.clone(), upload);
250        Ok(upload_id)
251    }
252
253    /// 上传单个分片
254    pub fn upload_part(
255        &self,
256        upload_id: &str,
257        number: u32,
258        data: Vec<u8>,
259    ) -> Result<String, StorageError> {
260        let mut sessions = self
261            .sessions
262            .lock()
263            .map_err(|e| StorageError::Connection(format!("lock error: {}", e)))?;
264        let upload = sessions
265            .get_mut(upload_id)
266            .ok_or_else(|| StorageError::NotFound(format!("upload {}", upload_id)))?;
267        upload.upload_part(number, data)
268    }
269
270    /// 完成上传
271    pub fn complete(&self, upload_id: &str) -> Result<Vec<u8>, StorageError> {
272        let mut sessions = self
273            .sessions
274            .lock()
275            .map_err(|e| StorageError::Connection(format!("lock error: {}", e)))?;
276        let upload = sessions
277            .get_mut(upload_id)
278            .ok_or_else(|| StorageError::NotFound(format!("upload {}", upload_id)))?;
279        let combined = upload.complete()?;
280        let key = upload.key.clone();
281        // 缓存完成的数据
282        if let Ok(mut completed) = self.completed.lock() {
283            completed.insert(key, combined.clone());
284        }
285        Ok(combined)
286    }
287
288    /// 中止上传
289    pub fn abort(&self, upload_id: &str) -> Result<(), StorageError> {
290        let mut sessions = self
291            .sessions
292            .lock()
293            .map_err(|e| StorageError::Connection(format!("lock error: {}", e)))?;
294        let upload = sessions
295            .get_mut(upload_id)
296            .ok_or_else(|| StorageError::NotFound(format!("upload {}", upload_id)))?;
297        upload.abort()
298    }
299
300    /// 获取上传会话状态(用于断点续传查询已上传分片)
301    pub fn get_session(&self, upload_id: &str) -> Option<MultipartUploadSnapshot> {
302        let sessions = self.sessions.lock().ok()?;
303        let upload = sessions.get(upload_id)?;
304        Some(MultipartUploadSnapshot {
305            upload_id: upload.upload_id.clone(),
306            key: upload.key.clone(),
307            content_type: upload.content_type.clone(),
308            uploaded_part_numbers: upload.parts.iter().map(|p| p.number).collect(),
309            expected_parts: upload.expected_parts,
310            part_size: upload.part_size,
311            status: upload.status,
312            uploaded_bytes: upload.uploaded_bytes(),
313            progress_percent: upload.progress_percent(),
314        })
315    }
316
317    /// 列出所有活跃的上传会话 ID(不包含已完成或已中止的)
318    pub fn list_uploads(&self) -> Vec<String> {
319        self.sessions
320            .lock()
321            .map(|s| {
322                s.iter()
323                    .filter(|(_, u)| {
324                        u.status != UploadStatus::Completed && u.status != UploadStatus::Aborted
325                    })
326                    .map(|(k, _)| k.clone())
327                    .collect()
328            })
329            .unwrap_or_default()
330    }
331
332    /// 获取已完成上传的数据
333    pub fn get_completed_data(&self, key: &str) -> Option<Vec<u8>> {
334        self.completed.lock().ok().and_then(|c| c.get(key).cloned())
335    }
336
337    /// 清理已完成或已中止的会话
338    pub fn cleanup(&self) -> usize {
339        let mut sessions = match self.sessions.lock() {
340            Ok(s) => s,
341            Err(_) => return 0,
342        };
343        let before = sessions.len();
344        sessions.retain(|_, u| {
345            u.status == UploadStatus::Initiated || u.status == UploadStatus::InProgress
346        });
347        before - sessions.len()
348    }
349}
350
351impl Default for ResumableUploadManager {
352    fn default() -> Self {
353        Self::new()
354    }
355}
356
357/// 分片上传状态快照(用于序列化和断点续传恢复)
358#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct MultipartUploadSnapshot {
360    pub upload_id: String,
361    pub key: String,
362    pub content_type: String,
363    /// 已上传的分片编号列表
364    pub uploaded_part_numbers: Vec<u32>,
365    pub expected_parts: u32,
366    pub part_size: usize,
367    pub status: UploadStatus,
368    pub uploaded_bytes: usize,
369    pub progress_percent: u8,
370}
371
372// ====================================================================
373// 存储桶生命周期管理(Bucket Lifecycle)
374// ====================================================================
375
376/// 生命周期动作类型
377#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
378pub enum LifecycleAction {
379    /// 转换存储类别(如 Standard -> IA -> Archive)
380    Transition {
381        /// 转换到的存储类别
382        storage_class: String,
383        /// 对象创建后多少天执行
384        days: u32,
385    },
386    /// 过期删除
387    Expiration {
388        /// 对象创建后多少天执行
389        days: u32,
390    },
391    /// 删除未完成的分片上传
392    AbortIncompleteMultipartUpload {
393        /// 发起后多少天执行
394        days: u32,
395    },
396}
397
398/// 生命周期规则
399#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct LifecycleRule {
401    /// 规则 ID
402    pub id: String,
403    /// 规则是否启用
404    pub enabled: bool,
405    /// 规则匹配的前缀(空表示匹配所有)
406    pub prefix: String,
407    /// 规则动作
408    pub action: LifecycleAction,
409}
410
411impl LifecycleRule {
412    pub fn new(id: impl Into<String>, action: LifecycleAction) -> Self {
413        Self {
414            id: id.into(),
415            enabled: true,
416            prefix: String::new(),
417            action,
418        }
419    }
420
421    pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
422        self.prefix = prefix.into();
423        self
424    }
425
426    pub fn disabled(mut self) -> Self {
427        self.enabled = false;
428        self
429    }
430
431    /// 判断给定 key 是否匹配此规则的前缀
432    pub fn matches(&self, key: &str) -> bool {
433        self.enabled && (self.prefix.is_empty() || key.starts_with(&self.prefix))
434    }
435}
436
437/// 存储桶生命周期管理器
438pub struct BucketLifecycle {
439    /// 规则列表
440    rules: Vec<LifecycleRule>,
441}
442
443impl BucketLifecycle {
444    pub fn new() -> Self {
445        Self { rules: Vec::new() }
446    }
447
448    /// 添加生命周期规则
449    pub fn add_rule(&mut self, rule: LifecycleRule) {
450        self.rules.push(rule);
451    }
452
453    /// 移除指定 ID 的规则
454    pub fn remove_rule(&mut self, id: &str) -> bool {
455        let before = self.rules.len();
456        self.rules.retain(|r| r.id != id);
457        self.rules.len() < before
458    }
459
460    /// 返回所有规则
461    pub fn rules(&self) -> &[LifecycleRule] {
462        &self.rules
463    }
464
465    /// 返回匹配指定 key 的所有规则
466    pub fn matching_rules(&self, key: &str) -> Vec<&LifecycleRule> {
467        self.rules.iter().filter(|r| r.matches(key)).collect()
468    }
469
470    /// 根据对象创建时间和当前时间,计算需要执行的动作
471    pub fn evaluate(
472        &self,
473        key: &str,
474        object_age_days: u32,
475        has_incomplete_upload: bool,
476    ) -> Vec<LifecycleEvaluationResult> {
477        let mut results = Vec::new();
478        for rule in self.matching_rules(key) {
479            match &rule.action {
480                LifecycleAction::Transition {
481                    storage_class,
482                    days,
483                } => {
484                    if object_age_days >= *days {
485                        results.push(LifecycleEvaluationResult {
486                            rule_id: rule.id.clone(),
487                            key: key.to_string(),
488                            action: LifecycleAction::Transition {
489                                storage_class: storage_class.clone(),
490                                days: *days,
491                            },
492                        });
493                    }
494                }
495                LifecycleAction::Expiration { days } => {
496                    if object_age_days >= *days {
497                        results.push(LifecycleEvaluationResult {
498                            rule_id: rule.id.clone(),
499                            key: key.to_string(),
500                            action: LifecycleAction::Expiration { days: *days },
501                        });
502                    }
503                }
504                LifecycleAction::AbortIncompleteMultipartUpload { days } => {
505                    if has_incomplete_upload {
506                        results.push(LifecycleEvaluationResult {
507                            rule_id: rule.id.clone(),
508                            key: key.to_string(),
509                            action: LifecycleAction::AbortIncompleteMultipartUpload { days: *days },
510                        });
511                    }
512                }
513            }
514        }
515        results
516    }
517
518    /// 返回规则数量
519    pub fn rule_count(&self) -> usize {
520        self.rules.len()
521    }
522
523    /// 启用指定规则
524    pub fn enable_rule(&mut self, id: &str) -> bool {
525        let mut found = false;
526        for rule in &mut self.rules {
527            if rule.id == id {
528                rule.enabled = true;
529                found = true;
530            }
531        }
532        found
533    }
534
535    /// 禁用指定规则
536    pub fn disable_rule(&mut self, id: &str) -> bool {
537        let mut found = false;
538        for rule in &mut self.rules {
539            if rule.id == id {
540                rule.enabled = false;
541                found = true;
542            }
543        }
544        found
545    }
546}
547
548impl Default for BucketLifecycle {
549    fn default() -> Self {
550        Self::new()
551    }
552}
553
554/// 生命周期评估结果
555#[derive(Debug, Clone, Serialize, Deserialize)]
556pub struct LifecycleEvaluationResult {
557    pub rule_id: String,
558    pub key: String,
559    pub action: LifecycleAction,
560}
561
562// ====================================================================
563// CDN 刷新(CDN Refresh / Purge)
564// ====================================================================
565
566/// CDN 刷新请求类型
567#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
568pub enum RefreshType {
569    /// 刷新单个 URL
570    Url,
571    /// 刷新整个目录
572    Directory,
573}
574
575/// CDN 刷新请求状态
576#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
577pub enum RefreshStatus {
578    /// 已提交,处理中
579    Pending,
580    /// 已完成
581    Done,
582    /// 失败
583    Failed,
584}
585
586/// CDN 刷新请求记录
587#[derive(Debug, Clone, Serialize, Deserialize)]
588pub struct RefreshRequest {
589    /// 请求 ID
590    pub request_id: String,
591    /// 刷新类型
592    pub refresh_type: RefreshType,
593    /// 刷新目标列表
594    pub targets: Vec<String>,
595    /// 提交时间戳(秒)
596    pub submitted_at: u64,
597    /// 状态
598    pub status: RefreshStatus,
599    /// 失败原因(如果有)
600    pub error: Option<String>,
601}
602
603impl RefreshRequest {
604    pub fn new(refresh_type: RefreshType, targets: Vec<String>) -> Self {
605        let now = SystemTime::now()
606            .duration_since(UNIX_EPOCH)
607            .unwrap_or_default()
608            .as_secs();
609        let counter = REFRESH_ID_COUNTER.fetch_add(1, Ordering::SeqCst);
610        Self {
611            request_id: format!("refresh-{}-{}", now, counter),
612            refresh_type,
613            targets,
614            submitted_at: now,
615            status: RefreshStatus::Pending,
616            error: None,
617        }
618    }
619}
620
621/// CDN 刷新器:提交并跟踪 CDN 缓存刷新请求
622pub struct CdnRefresher {
623    /// 刷新历史记录
624    history: Mutex<Vec<RefreshRequest>>,
625    /// 刷新请求速率限制:最近窗口内的请求数
626    rate_limit: Mutex<Vec<u64>>,
627    /// 每分钟最大刷新请求数
628    pub max_requests_per_minute: u32,
629    /// 每次请求最大 URL/目录数量
630    pub max_targets_per_request: u32,
631}
632
633impl CdnRefresher {
634    pub fn new() -> Self {
635        Self {
636            history: Mutex::new(Vec::new()),
637            rate_limit: Mutex::new(Vec::new()),
638            max_requests_per_minute: 100,
639            max_targets_per_request: 1000,
640        }
641    }
642
643    /// 自定义速率限制
644    pub fn with_rate_limit(mut self, max_per_minute: u32) -> Self {
645        self.max_requests_per_minute = max_per_minute;
646        self
647    }
648
649    /// 提交 URL 刷新请求
650    pub fn refresh_urls(&self, urls: Vec<String>) -> Result<String, StorageError> {
651        self.submit(RefreshType::Url, urls)
652    }
653
654    /// 提交目录刷新请求
655    pub fn refresh_dirs(&self, dirs: Vec<String>) -> Result<String, StorageError> {
656        self.submit(RefreshType::Directory, dirs)
657    }
658
659    /// 内部提交方法
660    fn submit(
661        &self,
662        refresh_type: RefreshType,
663        targets: Vec<String>,
664    ) -> Result<String, StorageError> {
665        if targets.is_empty() {
666            return Err(StorageError::InvalidConfig(
667                "refresh targets cannot be empty".to_string(),
668            ));
669        }
670        if targets.len() as u32 > self.max_targets_per_request {
671            return Err(StorageError::InvalidConfig(format!(
672                "too many targets: {} > {}",
673                targets.len(),
674                self.max_targets_per_request
675            )));
676        }
677        // 速率限制检查
678        self.check_rate_limit()?;
679        let mut request = RefreshRequest::new(refresh_type, targets);
680        // 模拟异步处理:立即标记为 Done
681        request.status = RefreshStatus::Done;
682        let request_id = request.request_id.clone();
683        let mut history = self
684            .history
685            .lock()
686            .map_err(|e| StorageError::Connection(format!("lock error: {}", e)))?;
687        history.push(request);
688        Ok(request_id)
689    }
690
691    /// 速率限制检查
692    fn check_rate_limit(&self) -> Result<(), StorageError> {
693        let now = SystemTime::now()
694            .duration_since(UNIX_EPOCH)
695            .unwrap_or_default()
696            .as_secs();
697        let mut rate = self
698            .rate_limit
699            .lock()
700            .map_err(|e| StorageError::Connection(format!("lock error: {}", e)))?;
701        // 移除 60 秒前的记录
702        rate.retain(|&t| now - t < 60);
703        if rate.len() as u32 >= self.max_requests_per_minute {
704            return Err(StorageError::InvalidConfig(format!(
705                "rate limit exceeded: {} requests in last minute",
706                rate.len()
707            )));
708        }
709        rate.push(now);
710        Ok(())
711    }
712
713    /// 查询刷新请求状态
714    pub fn get_status(&self, request_id: &str) -> Option<RefreshStatus> {
715        let history = self.history.lock().ok()?;
716        history
717            .iter()
718            .find(|r| r.request_id == request_id)
719            .map(|r| r.status)
720    }
721
722    /// 获取刷新请求详情
723    pub fn get_request(&self, request_id: &str) -> Option<RefreshRequest> {
724        let history = self.history.lock().ok()?;
725        history.iter().find(|r| r.request_id == request_id).cloned()
726    }
727
728    /// 返回所有刷新历史
729    pub fn history(&self) -> Vec<RefreshRequest> {
730        self.history.lock().map(|h| h.clone()).unwrap_or_default()
731    }
732
733    /// 返回刷新请求总数
734    pub fn total_requests(&self) -> usize {
735        self.history.lock().map(|h| h.len()).unwrap_or(0)
736    }
737
738    /// 返回最近 N 秒内的刷新请求数
739    pub fn requests_in_last(&self, seconds: u64) -> usize {
740        let now = SystemTime::now()
741            .duration_since(UNIX_EPOCH)
742            .unwrap_or_default()
743            .as_secs();
744        self.history
745            .lock()
746            .map(|h| h.iter().filter(|r| now - r.submitted_at < seconds).count())
747            .unwrap_or(0)
748    }
749
750    /// 预热 URL(将内容推送到 CDN 节点)
751    pub fn prefetch(&self, urls: Vec<String>) -> Result<String, StorageError> {
752        if urls.is_empty() {
753            return Err(StorageError::InvalidConfig(
754                "prefetch urls cannot be empty".to_string(),
755            ));
756        }
757        self.check_rate_limit()?;
758        let mut request = RefreshRequest::new(RefreshType::Url, urls);
759        request.status = RefreshStatus::Done;
760        let request_id = request.request_id.clone();
761        let mut history = self
762            .history
763            .lock()
764            .map_err(|e| StorageError::Connection(format!("lock error: {}", e)))?;
765        history.push(request);
766        Ok(request_id)
767    }
768}
769
770impl Default for CdnRefresher {
771    fn default() -> Self {
772        Self::new()
773    }
774}
775
776// ====================================================================
777// 辅助函数
778// ====================================================================
779
780/// 计算对象年龄(天)
781pub fn object_age_days(created_at: SystemTime) -> u32 {
782    let now = SystemTime::now();
783    match now.duration_since(created_at) {
784        Ok(d) => (d.as_secs() / 86400) as u32,
785        Err(_) => 0,
786    }
787}
788
789/// 将字节大小格式化为人类可读字符串
790pub fn format_size(bytes: usize) -> String {
791    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
792    let mut size = bytes as f64;
793    let mut unit_idx = 0;
794    while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
795        size /= 1024.0;
796        unit_idx += 1;
797    }
798    format!("{:.2} {}", size, UNITS[unit_idx])
799}
800
801/// 计算上传预估剩余时间(秒),基于已上传字节数和耗时
802pub fn estimate_remaining_seconds(
803    uploaded_bytes: usize,
804    total_bytes: usize,
805    elapsed: Duration,
806) -> Option<u64> {
807    if uploaded_bytes == 0 || total_bytes == 0 {
808        return None;
809    }
810    let elapsed_secs = elapsed.as_secs();
811    if elapsed_secs == 0 {
812        return None;
813    }
814    let speed = uploaded_bytes as f64 / elapsed_secs as f64;
815    if speed < 1.0 {
816        return None;
817    }
818    let remaining = (total_bytes - uploaded_bytes) as f64 / speed;
819    Some(remaining as u64)
820}
821
822#[cfg(test)]
823mod tests {
824    use super::*;
825
826    // ====================================================================
827    // MultipartUpload 测试
828    // ====================================================================
829
830    #[test]
831    fn test_multipart_upload_init() {
832        let upload = MultipartUpload::new("test.txt", "text/plain", 1024);
833        assert!(!upload.upload_id.is_empty());
834        assert_eq!(upload.key, "test.txt");
835        assert_eq!(upload.content_type, "text/plain");
836        assert_eq!(upload.part_size, 1024);
837        assert_eq!(upload.status, UploadStatus::Initiated);
838        assert_eq!(upload.uploaded_part_count(), 0);
839        assert_eq!(upload.uploaded_bytes(), 0);
840    }
841
842    #[test]
843    fn test_multipart_upload_single_part() {
844        let mut upload = MultipartUpload::new("file.txt", "text/plain", 100);
845        upload.expected_parts = 1;
846        let etag = upload.upload_part(1, b"hello".to_vec()).unwrap();
847        assert!(!etag.is_empty());
848        assert_eq!(upload.uploaded_part_count(), 1);
849        assert_eq!(upload.uploaded_bytes(), 5);
850        assert_eq!(upload.status, UploadStatus::InProgress);
851
852        let combined = upload.complete().unwrap();
853        assert_eq!(combined, b"hello");
854        assert_eq!(upload.status, UploadStatus::Completed);
855        assert!(upload.is_completed());
856    }
857
858    #[test]
859    fn test_multipart_upload_multiple_parts() {
860        let mut upload = MultipartUpload::new("big.bin", "application/octet-stream", 10);
861        upload.expected_parts = 3;
862        upload.upload_part(1, vec![0u8; 10]).unwrap();
863        upload.upload_part(2, vec![1u8; 10]).unwrap();
864        upload.upload_part(3, vec![2u8; 5]).unwrap(); // 最后一个分片可以小于 part_size
865        assert_eq!(upload.uploaded_part_count(), 3);
866        assert_eq!(upload.uploaded_bytes(), 25);
867
868        let combined = upload.complete().unwrap();
869        assert_eq!(combined.len(), 25);
870        assert_eq!(upload.progress_percent(), 100);
871    }
872
873    #[test]
874    fn test_multipart_upload_wrong_part_number() {
875        let mut upload = MultipartUpload::new("f", "text/plain", 10);
876        upload.expected_parts = 2;
877        upload.upload_part(1, vec![0u8; 10]).unwrap();
878        // 尝试上传编号 3 而不是 2
879        let result = upload.upload_part(3, vec![0u8; 10]);
880        assert!(result.is_err());
881    }
882
883    #[test]
884    fn test_multipart_upload_wrong_part_size() {
885        let mut upload = MultipartUpload::new("f", "text/plain", 10);
886        upload.expected_parts = 2;
887        // 第一个分片大小不对(不是最后一个分片)
888        let result = upload.upload_part(1, vec![0u8; 5]);
889        assert!(result.is_err());
890    }
891
892    #[test]
893    fn test_multipart_upload_complete_after_complete() {
894        let mut upload = MultipartUpload::new("f", "text/plain", 10);
895        upload.expected_parts = 1;
896        upload.upload_part(1, b"data".to_vec()).unwrap();
897        upload.complete().unwrap();
898        let result = upload.complete();
899        assert!(result.is_err());
900    }
901
902    #[test]
903    fn test_multipart_upload_abort() {
904        let mut upload = MultipartUpload::new("f", "text/plain", 10);
905        upload.expected_parts = 2;
906        upload.upload_part(1, vec![0u8; 10]).unwrap();
907        upload.abort().unwrap();
908        assert_eq!(upload.status, UploadStatus::Aborted);
909        assert_eq!(upload.uploaded_part_count(), 0);
910        // 中止后不能再上传
911        let result = upload.upload_part(2, vec![0u8; 10]);
912        assert!(result.is_err());
913    }
914
915    #[test]
916    fn test_multipart_upload_complete_empty() {
917        let mut upload = MultipartUpload::new("f", "text/plain", 10);
918        let result = upload.complete();
919        assert!(result.is_err());
920    }
921
922    #[test]
923    fn test_multipart_upload_progress() {
924        let mut upload = MultipartUpload::new("f", "text/plain", 10);
925        upload.expected_parts = 4;
926        assert_eq!(upload.progress_percent(), 0);
927        upload.upload_part(1, vec![0u8; 10]).unwrap();
928        assert_eq!(upload.progress_percent(), 25);
929        upload.upload_part(2, vec![0u8; 10]).unwrap();
930        assert_eq!(upload.progress_percent(), 50);
931        upload.upload_part(3, vec![0u8; 10]).unwrap();
932        assert_eq!(upload.progress_percent(), 75);
933    }
934
935    #[test]
936    fn test_multipart_upload_abort_after_complete_fails() {
937        let mut upload = MultipartUpload::new("f", "text/plain", 10);
938        upload.expected_parts = 1;
939        upload.upload_part(1, b"data".to_vec()).unwrap();
940        upload.complete().unwrap();
941        let result = upload.abort();
942        assert!(result.is_err());
943    }
944
945    // ====================================================================
946    // ResumableUploadManager 测试
947    // ====================================================================
948
949    #[test]
950    fn test_resumable_initiate() {
951        let mgr = ResumableUploadManager::new();
952        let upload_id = mgr
953            .initiate("file.bin", "application/octet-stream", 1000, 100)
954            .unwrap();
955        assert!(!upload_id.is_empty());
956        let session = mgr.get_session(&upload_id).unwrap();
957        assert_eq!(session.key, "file.bin");
958        assert_eq!(session.expected_parts, 10);
959        assert_eq!(session.part_size, 100);
960        assert_eq!(session.uploaded_part_numbers.len(), 0);
961    }
962
963    #[test]
964    fn test_resumable_upload_and_complete() {
965        let mgr = ResumableUploadManager::new();
966        let upload_id = mgr
967            .initiate("file.bin", "application/octet-stream", 200, 100)
968            .unwrap();
969        mgr.upload_part(&upload_id, 1, vec![0u8; 100]).unwrap();
970        mgr.upload_part(&upload_id, 2, vec![1u8; 100]).unwrap();
971        let combined = mgr.complete(&upload_id).unwrap();
972        assert_eq!(combined.len(), 200);
973        let data = mgr.get_completed_data("file.bin").unwrap();
974        assert_eq!(data.len(), 200);
975    }
976
977    #[test]
978    fn test_resumable_abort() {
979        let mgr = ResumableUploadManager::new();
980        let upload_id = mgr
981            .initiate("file.bin", "application/octet-stream", 200, 100)
982            .unwrap();
983        mgr.upload_part(&upload_id, 1, vec![0u8; 100]).unwrap();
984        mgr.abort(&upload_id).unwrap();
985        let session = mgr.get_session(&upload_id).unwrap();
986        assert_eq!(session.status, UploadStatus::Aborted);
987    }
988
989    #[test]
990    fn test_resumable_list_uploads() {
991        let mgr = ResumableUploadManager::new();
992        let id1 = mgr.initiate("a", "text/plain", 100, 50).unwrap();
993        let _id2 = mgr.initiate("b", "text/plain", 100, 50).unwrap();
994        assert_eq!(mgr.list_uploads().len(), 2);
995        // 上传分片后才能完成
996        mgr.upload_part(&id1, 1, vec![0u8; 50]).unwrap();
997        mgr.upload_part(&id1, 2, vec![0u8; 50]).unwrap();
998        mgr.complete(&id1).unwrap();
999        // 完成后不再出现在活跃列表
1000        assert_eq!(mgr.list_uploads().len(), 1);
1001    }
1002
1003    #[test]
1004    fn test_resumable_get_session_not_found() {
1005        let mgr = ResumableUploadManager::new();
1006        assert!(mgr.get_session("nonexistent").is_none());
1007    }
1008
1009    #[test]
1010    fn test_resumable_upload_part_invalid_session() {
1011        let mgr = ResumableUploadManager::new();
1012        let result = mgr.upload_part("nonexistent", 1, vec![0u8; 10]);
1013        assert!(result.is_err());
1014    }
1015
1016    #[test]
1017    fn test_resumable_complete_invalid_session() {
1018        let mgr = ResumableUploadManager::new();
1019        let result = mgr.complete("nonexistent");
1020        assert!(result.is_err());
1021    }
1022
1023    #[test]
1024    fn test_resumable_cleanup() {
1025        let mgr = ResumableUploadManager::new();
1026        let id1 = mgr.initiate("a", "text/plain", 100, 50).unwrap();
1027        let id2 = mgr.initiate("b", "text/plain", 100, 50).unwrap();
1028        // 上传分片后才能完成
1029        mgr.upload_part(&id1, 1, vec![0u8; 50]).unwrap();
1030        mgr.upload_part(&id1, 2, vec![0u8; 50]).unwrap();
1031        mgr.complete(&id1).unwrap();
1032        mgr.abort(&id2).unwrap();
1033        let cleaned = mgr.cleanup();
1034        assert_eq!(cleaned, 2);
1035    }
1036
1037    #[test]
1038    fn test_resumable_snapshot_has_uploaded_parts() {
1039        let mgr = ResumableUploadManager::new();
1040        let upload_id = mgr.initiate("f", "text/plain", 300, 100).unwrap();
1041        mgr.upload_part(&upload_id, 1, vec![0u8; 100]).unwrap();
1042        mgr.upload_part(&upload_id, 2, vec![0u8; 100]).unwrap();
1043        let snapshot = mgr.get_session(&upload_id).unwrap();
1044        assert_eq!(snapshot.uploaded_part_numbers, vec![1, 2]);
1045        assert_eq!(snapshot.uploaded_bytes, 200);
1046        assert_eq!(snapshot.progress_percent, 66);
1047    }
1048
1049    // ====================================================================
1050    // BucketLifecycle 测试
1051    // ====================================================================
1052
1053    #[test]
1054    fn test_lifecycle_add_and_remove_rule() {
1055        let mut lc = BucketLifecycle::new();
1056        lc.add_rule(LifecycleRule::new(
1057            "expire-30d",
1058            LifecycleAction::Expiration { days: 30 },
1059        ));
1060        assert_eq!(lc.rule_count(), 1);
1061        assert!(lc.remove_rule("expire-30d"));
1062        assert_eq!(lc.rule_count(), 0);
1063        assert!(!lc.remove_rule("nonexistent"));
1064    }
1065
1066    #[test]
1067    fn test_lifecycle_rule_matches_prefix() {
1068        let rule = LifecycleRule::new(
1069            "logs",
1070            LifecycleAction::Transition {
1071                storage_class: "IA".to_string(),
1072                days: 30,
1073            },
1074        )
1075        .with_prefix("logs/");
1076        assert!(rule.matches("logs/app.log"));
1077        assert!(!rule.matches("images/photo.jpg"));
1078    }
1079
1080    #[test]
1081    fn test_lifecycle_rule_empty_prefix_matches_all() {
1082        let rule = LifecycleRule::new("all", LifecycleAction::Expiration { days: 365 });
1083        assert!(rule.matches("anything"));
1084        assert!(rule.matches("path/to/file"));
1085    }
1086
1087    #[test]
1088    fn test_lifecycle_rule_disabled_does_not_match() {
1089        let rule = LifecycleRule::new("r", LifecycleAction::Expiration { days: 30 }).disabled();
1090        assert!(!rule.matches("any"));
1091    }
1092
1093    #[test]
1094    fn test_lifecycle_evaluate_expiration() {
1095        let mut lc = BucketLifecycle::new();
1096        lc.add_rule(LifecycleRule::new(
1097            "expire-30d",
1098            LifecycleAction::Expiration { days: 30 },
1099        ));
1100        // 20 天 -> 不应过期
1101        let results = lc.evaluate("file.txt", 20, false);
1102        assert!(results.is_empty());
1103        // 35 天 -> 应过期
1104        let results = lc.evaluate("file.txt", 35, false);
1105        assert_eq!(results.len(), 1);
1106        assert!(matches!(
1107            results[0].action,
1108            LifecycleAction::Expiration { .. }
1109        ));
1110    }
1111
1112    #[test]
1113    fn test_lifecycle_evaluate_transition() {
1114        let mut lc = BucketLifecycle::new();
1115        lc.add_rule(LifecycleRule::new(
1116            "to-ia",
1117            LifecycleAction::Transition {
1118                storage_class: "IA".to_string(),
1119                days: 30,
1120            },
1121        ));
1122        let results = lc.evaluate("data.bin", 40, false);
1123        assert_eq!(results.len(), 1);
1124        if let LifecycleAction::Transition { storage_class, .. } = &results[0].action {
1125            assert_eq!(storage_class, "IA");
1126        } else {
1127            panic!("expected Transition action");
1128        }
1129    }
1130
1131    #[test]
1132    fn test_lifecycle_evaluate_abort_incomplete() {
1133        let mut lc = BucketLifecycle::new();
1134        lc.add_rule(LifecycleRule::new(
1135            "abort-multipart",
1136            LifecycleAction::AbortIncompleteMultipartUpload { days: 7 },
1137        ));
1138        // 没有未完成上传 -> 不触发
1139        let results = lc.evaluate("file", 0, false);
1140        assert!(results.is_empty());
1141        // 有未完成上传 -> 触发
1142        let results = lc.evaluate("file", 0, true);
1143        assert_eq!(results.len(), 1);
1144    }
1145
1146    #[test]
1147    fn test_lifecycle_enable_disable_rule() {
1148        let mut lc = BucketLifecycle::new();
1149        lc.add_rule(LifecycleRule::new(
1150            "r",
1151            LifecycleAction::Expiration { days: 30 },
1152        ));
1153        assert!(lc.disable_rule("r"));
1154        assert!(!lc.matching_rules("file").iter().any(|r| r.id == "r"));
1155        assert!(lc.enable_rule("r"));
1156        assert!(lc.matching_rules("file").iter().any(|r| r.id == "r"));
1157    }
1158
1159    #[test]
1160    fn test_lifecycle_matching_rules_filtered_by_prefix() {
1161        let mut lc = BucketLifecycle::new();
1162        lc.add_rule(
1163            LifecycleRule::new("logs", LifecycleAction::Expiration { days: 30 })
1164                .with_prefix("logs/"),
1165        );
1166        lc.add_rule(
1167            LifecycleRule::new("imgs", LifecycleAction::Expiration { days: 60 })
1168                .with_prefix("images/"),
1169        );
1170        let matches = lc.matching_rules("logs/app.log");
1171        assert_eq!(matches.len(), 1);
1172        assert_eq!(matches[0].id, "logs");
1173    }
1174
1175    #[test]
1176    fn test_lifecycle_multiple_rules_match_same_key() {
1177        let mut lc = BucketLifecycle::new();
1178        lc.add_rule(LifecycleRule::new(
1179            "to-ia",
1180            LifecycleAction::Transition {
1181                storage_class: "IA".to_string(),
1182                days: 30,
1183            },
1184        ));
1185        lc.add_rule(LifecycleRule::new(
1186            "expire",
1187            LifecycleAction::Expiration { days: 365 },
1188        ));
1189        let results = lc.evaluate("file", 400, false);
1190        assert_eq!(results.len(), 2);
1191    }
1192
1193    // ====================================================================
1194    // CdnRefresher 测试
1195    // ====================================================================
1196
1197    #[test]
1198    fn test_cdn_refresh_urls() {
1199        let refresher = CdnRefresher::new();
1200        let id = refresher
1201            .refresh_urls(vec![
1202                "https://cdn.example.com/a.js".to_string(),
1203                "https://cdn.example.com/b.css".to_string(),
1204            ])
1205            .unwrap();
1206        assert!(!id.is_empty());
1207        assert_eq!(refresher.total_requests(), 1);
1208        let status = refresher.get_status(&id).unwrap();
1209        assert_eq!(status, RefreshStatus::Done);
1210    }
1211
1212    #[test]
1213    fn test_cdn_refresh_dirs() {
1214        let refresher = CdnRefresher::new();
1215        let id = refresher
1216            .refresh_dirs(vec!["https://cdn.example.com/static/".to_string()])
1217            .unwrap();
1218        let request = refresher.get_request(&id).unwrap();
1219        assert_eq!(request.refresh_type, RefreshType::Directory);
1220        assert_eq!(request.targets.len(), 1);
1221    }
1222
1223    #[test]
1224    fn test_cdn_refresh_empty_targets() {
1225        let refresher = CdnRefresher::new();
1226        let result = refresher.refresh_urls(vec![]);
1227        assert!(result.is_err());
1228    }
1229
1230    #[test]
1231    fn test_cdn_refresh_too_many_targets() {
1232        let refresher = CdnRefresher::new().with_rate_limit(100);
1233        let urls: Vec<String> = (0..2000)
1234            .map(|i| format!("https://cdn.example.com/{}.js", i))
1235            .collect();
1236        let result = refresher.refresh_urls(urls);
1237        assert!(result.is_err());
1238    }
1239
1240    #[test]
1241    fn test_cdn_refresh_history() {
1242        let refresher = CdnRefresher::new();
1243        refresher
1244            .refresh_urls(vec!["https://cdn.example.com/a".to_string()])
1245            .unwrap();
1246        refresher
1247            .refresh_urls(vec!["https://cdn.example.com/b".to_string()])
1248            .unwrap();
1249        refresher
1250            .refresh_dirs(vec!["https://cdn.example.com/static/".to_string()])
1251            .unwrap();
1252        let history = refresher.history();
1253        assert_eq!(history.len(), 3);
1254    }
1255
1256    #[test]
1257    fn test_cdn_get_status_not_found() {
1258        let refresher = CdnRefresher::new();
1259        assert!(refresher.get_status("nonexistent").is_none());
1260    }
1261
1262    #[test]
1263    fn test_cdn_get_request_not_found() {
1264        let refresher = CdnRefresher::new();
1265        assert!(refresher.get_request("nonexistent").is_none());
1266    }
1267
1268    #[test]
1269    fn test_cdn_prefetch() {
1270        let refresher = CdnRefresher::new();
1271        let id = refresher
1272            .prefetch(vec!["https://cdn.example.com/big-file.zip".to_string()])
1273            .unwrap();
1274        assert_eq!(refresher.get_status(&id).unwrap(), RefreshStatus::Done);
1275    }
1276
1277    #[test]
1278    fn test_cdn_prefetch_empty() {
1279        let refresher = CdnRefresher::new();
1280        let result = refresher.prefetch(vec![]);
1281        assert!(result.is_err());
1282    }
1283
1284    #[test]
1285    fn test_cdn_rate_limit() {
1286        let refresher = CdnRefresher::new().with_rate_limit(3);
1287        refresher
1288            .refresh_urls(vec!["https://a.com".to_string()])
1289            .unwrap();
1290        refresher
1291            .refresh_urls(vec!["https://b.com".to_string()])
1292            .unwrap();
1293        refresher
1294            .refresh_urls(vec!["https://c.com".to_string()])
1295            .unwrap();
1296        // 第 4 次应被速率限制拒绝
1297        let result = refresher.refresh_urls(vec!["https://d.com".to_string()]);
1298        assert!(result.is_err());
1299    }
1300
1301    #[test]
1302    fn test_cdn_requests_in_last() {
1303        let refresher = CdnRefresher::new();
1304        refresher
1305            .refresh_urls(vec!["https://a.com".to_string()])
1306            .unwrap();
1307        refresher
1308            .refresh_urls(vec!["https://b.com".to_string()])
1309            .unwrap();
1310        assert_eq!(refresher.requests_in_last(60), 2);
1311    }
1312
1313    // ====================================================================
1314    // 辅助函数测试
1315    // ====================================================================
1316
1317    #[test]
1318    fn test_format_size() {
1319        assert_eq!(format_size(0), "0.00 B");
1320        assert_eq!(format_size(512), "512.00 B");
1321        assert_eq!(format_size(1024), "1.00 KB");
1322        assert_eq!(format_size(1048576), "1.00 MB");
1323        assert_eq!(format_size(1073741824), "1.00 GB");
1324    }
1325
1326    #[test]
1327    fn test_estimate_remaining_seconds() {
1328        // 上传 100 字节用了 10 秒,还剩 100 字节 -> 预计还需 10 秒
1329        let remaining = estimate_remaining_seconds(100, 200, Duration::from_secs(10));
1330        assert_eq!(remaining, Some(10));
1331    }
1332
1333    #[test]
1334    fn test_estimate_remaining_seconds_zero_uploaded() {
1335        let remaining = estimate_remaining_seconds(0, 100, Duration::from_secs(5));
1336        assert_eq!(remaining, None);
1337    }
1338
1339    #[test]
1340    fn test_estimate_remaining_seconds_zero_elapsed() {
1341        let remaining = estimate_remaining_seconds(50, 100, Duration::from_secs(0));
1342        assert_eq!(remaining, None);
1343    }
1344
1345    #[test]
1346    fn test_object_age_days() {
1347        let one_day_ago = SystemTime::now() - Duration::from_secs(86400);
1348        let age = object_age_days(one_day_ago);
1349        assert!(age >= 1);
1350    }
1351
1352    #[test]
1353    fn test_part_etag_unique() {
1354        let p1 = Part::new(1, vec![0u8; 10]);
1355        let p2 = Part::new(2, vec![0u8; 10]);
1356        let p3 = Part::new(1, vec![0u8; 20]);
1357        assert_ne!(p1.etag, p2.etag);
1358        assert_ne!(p1.etag, p3.etag);
1359    }
1360}