Skip to content

Commit 3fe1365

Browse files
committed
fix: #461 support s3 path style addressing
1 parent aa6904a commit 3fe1365

3 files changed

Lines changed: 61 additions & 53 deletions

File tree

core/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"s3_region_name": "auto",
2727
"s3_signature_version": "s3v2",
2828
"s3_hostname": "",
29+
"s3_addressing_style": "auto",
2930
"s3_proxy": 0,
3031
"max_save_seconds": 0,
3132
"aws_session_token": "",

core/storage.py

Lines changed: 27 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ def __init__(self):
267267
self.signature_version = settings.s3_signature_version
268268
self.endpoint_url = settings.s3_endpoint_url or f"https://{self.s3_hostname}"
269269
self.aws_session_token = settings.aws_session_token
270+
self.addressing_style = str(settings.s3_addressing_style or "auto").lower()
270271
self.proxy = settings.s3_proxy
271272
self.session = aioboto3.Session(
272273
aws_access_key_id=self.access_key_id,
@@ -278,14 +279,26 @@ def __init__(self):
278279
# 如果提供了 s3_endpoint_url,则优先使用它
279280
self.endpoint_url = settings.s3_endpoint_url
280281

282+
def _client_config(self) -> Config:
283+
config = {"signature_version": self.signature_version}
284+
s3_config = {}
285+
if self.addressing_style in {"path", "virtual", "auto"}:
286+
s3_config["addressing_style"] = self.addressing_style
287+
if s3_config:
288+
config["s3"] = s3_config
289+
return Config(**config)
290+
291+
def _client(self):
292+
return self.session.client(
293+
"s3",
294+
endpoint_url=self.endpoint_url,
295+
aws_session_token=self.aws_session_token,
296+
region_name=self.region_name,
297+
config=self._client_config(),
298+
)
299+
281300
async def save_file(self, file: UploadFile, save_path: str):
282-
async with self.session.client(
283-
"s3",
284-
endpoint_url=self.endpoint_url,
285-
aws_session_token=self.aws_session_token,
286-
region_name=self.region_name,
287-
config=Config(signature_version=self.signature_version),
288-
) as s3:
301+
async with self._client() as s3:
289302
# 使用 upload_fileobj 流式上传,避免将整个文件加载到内存
290303
await s3.upload_fileobj(
291304
file.file,
@@ -295,12 +308,7 @@ async def save_file(self, file: UploadFile, save_path: str):
295308
)
296309

297310
async def delete_file(self, file_code: FileCodes):
298-
async with self.session.client(
299-
"s3",
300-
endpoint_url=self.endpoint_url,
301-
region_name=self.region_name,
302-
config=Config(signature_version=self.signature_version),
303-
) as s3:
311+
async with self._client() as s3:
304312
await s3.delete_object(
305313
Bucket=self.bucket_name, Key=await file_code.get_file_path()
306314
)
@@ -310,12 +318,7 @@ async def get_file_response(self, file_code: FileCodes):
310318
filename = file_code.prefix + file_code.suffix
311319
content_length = None # 初始化为 None,表示未知大小
312320

313-
async with self.session.client(
314-
"s3",
315-
endpoint_url=self.endpoint_url,
316-
region_name=self.region_name,
317-
config=Config(signature_version=self.signature_version),
318-
) as s3:
321+
async with self._client() as s3:
319322
# 尝试获取文件大小(HEAD请求)
320323
try:
321324
head_response = await s3.head_object(
@@ -384,12 +387,7 @@ async def get_file_url(self, file_code: FileCodes):
384387
if self.proxy:
385388
return await get_file_url(file_code.code)
386389
else:
387-
async with self.session.client(
388-
"s3",
389-
endpoint_url=self.endpoint_url,
390-
region_name=self.region_name,
391-
config=Config(signature_version=self.signature_version),
392-
) as s3:
390+
async with self._client() as s3:
393391
result = await s3.generate_presigned_url(
394392
"get_object",
395393
Params={
@@ -406,13 +404,7 @@ async def save_chunk(self, upload_id: str, chunk_index: int, chunk_data: bytes,
406404
注意:这里不使用 S3 原生的 multipart upload,而是将每个分片作为独立对象存储
407405
"""
408406
chunk_key = str(Path(save_path).parent / "chunks" / upload_id / f"{chunk_index}.part")
409-
async with self.session.client(
410-
's3',
411-
endpoint_url=self.endpoint_url,
412-
aws_session_token=self.aws_session_token,
413-
region_name=self.region_name,
414-
config=Config(signature_version=self.signature_version),
415-
) as s3:
407+
async with self._client() as s3:
416408
# 将分片作为独立对象上传
417409
await s3.put_object(
418410
Bucket=self.bucket_name,
@@ -432,13 +424,7 @@ async def merge_chunks(self, upload_id: str, chunk_info: UploadChunk, save_path:
432424
file_sha256 = hashlib.sha256()
433425
chunk_dir = str(Path(save_path).parent / "chunks" / upload_id)
434426

435-
async with self.session.client(
436-
's3',
437-
endpoint_url=self.endpoint_url,
438-
aws_session_token=self.aws_session_token,
439-
region_name=self.region_name,
440-
config=Config(signature_version=self.signature_version),
441-
) as s3:
427+
async with self._client() as s3:
442428
# 创建 multipart upload
443429
mpu = await s3.create_multipart_upload(
444430
Bucket=self.bucket_name,
@@ -512,13 +498,7 @@ async def clean_chunks(self, upload_id: str, save_path: str):
512498
:param save_path: 文件保存路径
513499
"""
514500
chunk_dir = str(Path(save_path).parent / "chunks" / upload_id)
515-
async with self.session.client(
516-
's3',
517-
endpoint_url=self.endpoint_url,
518-
aws_session_token=self.aws_session_token,
519-
region_name=self.region_name,
520-
config=Config(signature_version=self.signature_version),
521-
) as s3:
501+
async with self._client() as s3:
522502
try:
523503
# 列出并删除所有分片对象
524504
paginator = s3.get_paginator('list_objects_v2')
@@ -540,13 +520,7 @@ async def generate_presigned_upload_url(self, save_path: str, expires_in: int =
540520
:param expires_in: URL过期时间(秒),默认15分钟
541521
:return: 预签名PUT URL
542522
"""
543-
async with self.session.client(
544-
"s3",
545-
endpoint_url=self.endpoint_url,
546-
aws_session_token=self.aws_session_token,
547-
region_name=self.region_name,
548-
config=Config(signature_version=self.signature_version),
549-
) as s3:
523+
async with self._client() as s3:
550524
return await s3.generate_presigned_url(
551525
"put_object",
552526
Params={
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import unittest
2+
3+
from core.settings import settings
4+
from core.storage import S3FileStorage
5+
6+
7+
class SettingsOverrideMixin:
8+
def setUp(self):
9+
self._original_user_config = dict(settings.user_config)
10+
11+
def tearDown(self):
12+
settings.user_config = self._original_user_config
13+
14+
15+
class S3StorageConfigTests(SettingsOverrideMixin, unittest.TestCase):
16+
def test_client_config_uses_path_style_when_configured(self):
17+
storage = S3FileStorage.__new__(S3FileStorage)
18+
storage.signature_version = "s3v4"
19+
storage.addressing_style = "path"
20+
21+
config = storage._client_config()
22+
23+
self.assertEqual(config.signature_version, "s3v4")
24+
self.assertEqual(config.s3["addressing_style"], "path")
25+
26+
def test_client_config_ignores_invalid_addressing_style(self):
27+
storage = S3FileStorage.__new__(S3FileStorage)
28+
storage.signature_version = "s3v4"
29+
storage.addressing_style = "invalid"
30+
31+
config = storage._client_config()
32+
33+
self.assertIsNone(config.s3)

0 commit comments

Comments
 (0)