| 107 | |
| 108 | |
| 109 | class SystemFileStorage(FileStorageInterface): |
| 110 | def __init__(self): |
| 111 | self.chunk_size = 256 * 1024 |
| 112 | self.root_path = data_root |
| 113 | |
| 114 | def _save(self, file, save_path): |
| 115 | with open(save_path, "wb") as f: |
| 116 | chunk = file.read(self.chunk_size) |
| 117 | while chunk: |
| 118 | f.write(chunk) |
| 119 | chunk = file.read(self.chunk_size) |
| 120 | |
| 121 | async def save_file(self, file: UploadFile, save_path: str): |
| 122 | path_obj = Path(save_path) |
| 123 | directory = str(path_obj.parent) |
| 124 | # 提取原始文件名并进行清理 |
| 125 | filename = await sanitize_filename(path_obj.name) |
| 126 | # 构建安全的完整保存路径 |
| 127 | safe_save_path = self.root_path / directory / filename |
| 128 | # 确保目录存在 |
| 129 | if not safe_save_path.parent.exists(): |
| 130 | safe_save_path.parent.mkdir(parents=True) |
| 131 | await asyncio.to_thread(self._save, file.file, safe_save_path) |
| 132 | |
| 133 | async def delete_file(self, file_code: FileCodes): |
| 134 | save_path = self.root_path / await file_code.get_file_path() |
| 135 | if save_path.exists(): |
| 136 | save_path.unlink() |
| 137 | |
| 138 | async def get_file_url(self, file_code: FileCodes): |
| 139 | return await get_file_url(file_code.code) |
| 140 | |
| 141 | async def get_file_response(self, file_code: FileCodes): |
| 142 | file_path = self.root_path / await file_code.get_file_path() |
| 143 | if not file_path.exists(): |
| 144 | return APIResponse(code=404, detail="文件已过期删除") |
| 145 | filename = f"{file_code.prefix}{file_code.suffix}" |
| 146 | encoded_filename = quote(filename, safe='') |
| 147 | content_disposition = f"attachment; filename*=UTF-8''{encoded_filename}" |
| 148 | |
| 149 | # 尝试获取文件系统大小,如果成功则设置 Content-Length |
| 150 | headers = {"Content-Disposition": content_disposition} |
| 151 | try: |
| 152 | content_length = file_path.stat().st_size |
| 153 | headers["Content-Length"] = str(content_length) |
| 154 | except Exception: |
| 155 | # 如果获取文件大小失败,则不提供 Content-Length |
| 156 | pass |
| 157 | |
| 158 | return FileResponse( |
| 159 | file_path, |
| 160 | media_type="application/octet-stream", |
| 161 | headers=headers, |
| 162 | filename=filename # 保留原始文件名以备某些场景使用 |
| 163 | ) |
| 164 | |
| 165 | async def save_chunk(self, upload_id: str, chunk_index: int, chunk_data: bytes, chunk_hash: str, save_path: str): |
| 166 | """ |
nothing calls this directly
no outgoing calls
no test coverage detected