| 860 | |
| 861 | |
| 862 | class OpenDALFileStorage(FileStorageInterface): |
| 863 | def __init__(self): |
| 864 | try: |
| 865 | import opendal |
| 866 | except ImportError: |
| 867 | raise ImportError('请先安装 `opendal`, 例如: "pip install opendal"') |
| 868 | self.service = settings.opendal_scheme |
| 869 | service_settings = {} |
| 870 | for key, value in settings.items(): |
| 871 | if key.startswith("opendal_" + self.service): |
| 872 | setting_name = key.split("_", 2)[2] |
| 873 | service_settings[setting_name] = value |
| 874 | self.operator = opendal.AsyncOperator( |
| 875 | settings.opendal_scheme, **service_settings |
| 876 | ) |
| 877 | |
| 878 | async def save_file(self, file: UploadFile, save_path: str): |
| 879 | # 使用 asyncio.to_thread 避免阻塞事件循环 |
| 880 | content = await asyncio.to_thread(file.file.read) |
| 881 | await self.operator.write(save_path, content) |
| 882 | |
| 883 | async def delete_file(self, file_code: FileCodes): |
| 884 | await self.operator.delete(await file_code.get_file_path()) |
| 885 | |
| 886 | async def get_file_url(self, file_code: FileCodes): |
| 887 | return await get_file_url(file_code.code) |
| 888 | |
| 889 | async def get_file_response(self, file_code: FileCodes): |
| 890 | try: |
| 891 | filename = file_code.prefix + file_code.suffix |
| 892 | content_length = None # 初始化为 None,表示未知大小 |
| 893 | |
| 894 | # 尝试获取文件大小 |
| 895 | try: |
| 896 | stat_result = await self.operator.stat(await file_code.get_file_path()) |
| 897 | if hasattr(stat_result, 'content_length') and stat_result.content_length: |
| 898 | content_length = stat_result.content_length |
| 899 | elif hasattr(stat_result, 'size') and stat_result.size: |
| 900 | content_length = stat_result.size |
| 901 | except Exception: |
| 902 | # 如果获取大小失败,则不提供 Content-Length |
| 903 | pass |
| 904 | |
| 905 | # 尝试使用流式读取器 |
| 906 | try: |
| 907 | # OpenDAL 可能提供 reader 方法返回一个异步读取器 |
| 908 | reader = await self.operator.reader(await file_code.get_file_path()) |
| 909 | except AttributeError: |
| 910 | # 如果 reader 方法不存在,回退到全量读取(兼容旧版本) |
| 911 | content = await self.operator.read(await file_code.get_file_path()) |
| 912 | encoded_filename = quote(filename, safe='') |
| 913 | headers = { |
| 914 | "Content-Disposition": f"attachment; filename*=UTF-8''{encoded_filename}" |
| 915 | } |
| 916 | if content_length is not None: |
| 917 | headers["Content-Length"] = str(content_length) |
| 918 | return Response( |
| 919 | content, headers=headers, media_type="application/octet-stream" |
nothing calls this directly
no outgoing calls
no test coverage detected