fix(file and app):

1. Handle the encoding issue when downloading Markdown files;
2. Experience the sharing of memory configuration
This commit is contained in:
Timebomb2018
2026-03-25 17:54:27 +08:00
parent def7367e33
commit caab58dd2f
7 changed files with 57 additions and 30 deletions

View File

@@ -574,8 +574,12 @@ async def get_file_url(
# For local storage, generate signed URL with expiration # For local storage, generate signed URL with expiration
url = generate_signed_url(str(file_id), expires) url = generate_signed_url(str(file_id), expires)
else: else:
# For remote storage (OSS/S3), get presigned URL # For remote storage (OSS/S3), get presigned URL with forced download
url = await storage_service.get_file_url(file_key, expires=expires) url = await storage_service.get_file_url(
file_key,
expires=expires,
file_name=file_metadata.file_name,
)
url = _match_scheme(request, url) url = _match_scheme(request, url)
api_logger.info(f"Generated file URL: file_id={file_id}") api_logger.info(f"Generated file URL: file_id={file_id}")
@@ -786,7 +790,7 @@ async def permanent_download_file(
# For remote storage, redirect to presigned URL with long expiration # For remote storage, redirect to presigned URL with long expiration
try: try:
# Use a very long expiration (7 days max for most cloud providers) # Use a very long expiration (7 days max for most cloud providers)
presigned_url = await storage_service.get_file_url(file_key, expires=604800) presigned_url = await storage_service.get_file_url(file_key, expires=604800, file_name=file_metadata.file_name)
presigned_url = _match_scheme(request, presigned_url) presigned_url = _match_scheme(request, presigned_url)
return RedirectResponse(url=presigned_url, status_code=status.HTTP_302_FOUND) return RedirectResponse(url=presigned_url, status_code=status.HTTP_302_FOUND)
except Exception as e: except Exception as e:

View File

@@ -669,6 +669,7 @@ async def config_query(
content = { content = {
"app_type": release.app.type, "app_type": release.app.type,
"variables": release.config.get("variables"), "variables": release.config.get("variables"),
"memory": release.config.get("memory", {}).get("enabled"),
"features": release.config.get("features") "features": release.config.get("features")
} }
elif release.app.type == AppType.MULTI_AGENT: elif release.app.type == AppType.MULTI_AGENT:

View File

@@ -109,17 +109,13 @@ class StorageBackend(ABC):
pass pass
@abstractmethod @abstractmethod
async def get_url(self, file_key: str, expires: int = 3600) -> str: async def get_url(
""" self,
Get an access URL for the file. file_key: str,
expires: int = 3600,
Args: file_name: Optional[str] = None
file_key: Unique identifier for the file in the storage system. ) -> str:
expires: URL validity period in seconds (default: 1 hour). """Get an access URL for the file."""
Returns:
URL for accessing the file.
"""
pass pass
async def get_permanent_url(self, file_key: str) -> Optional[str]: async def get_permanent_url(self, file_key: str) -> Optional[str]:

View File

@@ -210,7 +210,12 @@ class LocalStorage(StorageBackend):
cause=e, cause=e,
) )
async def get_url(self, file_key: str, expires: int = 3600) -> str: async def get_url(
self,
file_key: str,
expires: int = 3600,
file_name: Optional[str] = None
) -> str:
""" """
Get an access URL for the file. Get an access URL for the file.
@@ -220,6 +225,7 @@ class LocalStorage(StorageBackend):
Args: Args:
file_key: Unique identifier for the file in the storage system. file_key: Unique identifier for the file in the storage system.
expires: URL validity period in seconds (not used for local storage). expires: URL validity period in seconds (not used for local storage).
file_name: If set, adds Content-Disposition: attachment to force download.
Returns: Returns:
A relative URL path for accessing the file. A relative URL path for accessing the file.

View File

@@ -7,6 +7,7 @@ Storage Service (OSS) using the oss2 SDK.
import io import io
import logging import logging
import urllib.parse
from typing import AsyncIterator, Optional from typing import AsyncIterator, Optional
import oss2 import oss2
@@ -242,24 +243,33 @@ class OSSStorage(StorageBackend):
logger.error(f"Failed to check file existence in OSS {file_key}: {e}") logger.error(f"Failed to check file existence in OSS {file_key}: {e}")
return False return False
async def get_url(self, file_key: str, expires: int = 3600) -> str: async def get_url(
self,
file_key: str,
expires: int = 3600,
file_name: Optional[str] = None,
) -> str:
""" """
Get a presigned URL for accessing the file. Get a presigned URL for accessing the file.
Args: Args:
file_key: Unique identifier for the file in the storage system. file_key: Unique identifier for the file in the storage system.
expires: URL validity period in seconds (default: 1 hour). expires: URL validity period in seconds (default: 1 hour).
file_name: If set, adds Content-Disposition: attachment to force download.
Returns: Returns:
A presigned URL for accessing the file. A presigned URL for accessing the file.
""" """
try: try:
url = self.bucket.sign_url("GET", file_key, expires) params = {}
if file_name:
filename_encoded = urllib.parse.quote(file_name.encode("utf-8"))
params["response-content-disposition"] = f"attachment; filename*=UTF-8''{filename_encoded}"
url = self.bucket.sign_url("GET", file_key, expires, params=params if params else None)
logger.debug(f"Generated presigned URL for {file_key}, expires in {expires}s") logger.debug(f"Generated presigned URL for {file_key}, expires in {expires}s")
return url return url
except Exception as e: except Exception as e:
logger.error(f"Failed to generate presigned URL for {file_key}: {e}") logger.error(f"Failed to generate presigned URL for {file_key}: {e}")
# Return a basic URL format as fallback
return f"https://{self.bucket_name}.{self.endpoint.replace('https://', '').replace('http://', '')}/{file_key}" return f"https://{self.bucket_name}.{self.endpoint.replace('https://', '').replace('http://', '')}/{file_key}"
async def get_permanent_url(self, file_key: str) -> str: async def get_permanent_url(self, file_key: str) -> str:

View File

@@ -6,6 +6,7 @@ using the boto3 SDK.
""" """
import io import io
import urllib.parse
import logging import logging
from typing import AsyncIterator, Optional from typing import AsyncIterator, Optional
@@ -352,31 +353,37 @@ class S3Storage(StorageBackend):
logger.error(f"Failed to check file existence in S3 {file_key}: {e}") logger.error(f"Failed to check file existence in S3 {file_key}: {e}")
return False return False
async def get_url(self, file_key: str, expires: int = 3600) -> str: async def get_url(
self,
file_key: str,
expires: int = 3600,
file_name: Optional[str] = None,
) -> str:
""" """
Get a presigned URL for accessing the file. Get a presigned URL for accessing the file.
Args: Args:
file_key: Unique identifier for the file in the storage system. file_key: Unique identifier for the file in the storage system.
expires: URL validity period in seconds (default: 1 hour). expires: URL validity period in seconds (default: 1 hour).
file_name: If set, adds Content-Disposition: attachment to force download.
Returns: Returns:
A presigned URL for accessing the file. A presigned URL for accessing the file.
""" """
try: try:
params = {"Bucket": self.bucket_name, "Key": file_key}
if file_name:
filename_encoded = urllib.parse.quote(file_name.encode("utf-8"))
params["ResponseContentDisposition"] = f"attachment; filename*=UTF-8''{filename_encoded}"
url = self.client.generate_presigned_url( url = self.client.generate_presigned_url(
"get_object", "get_object",
Params={ Params=params,
"Bucket": self.bucket_name,
"Key": file_key,
},
ExpiresIn=expires, ExpiresIn=expires,
) )
logger.debug(f"Generated presigned URL for {file_key}, expires in {expires}s") logger.debug(f"Generated presigned URL for {file_key}, expires in {expires}s")
return url return url
except Exception as e: except Exception as e:
logger.error(f"Failed to generate presigned URL for {file_key}: {e}") logger.error(f"Failed to generate presigned URL for {file_key}: {e}")
# Return a basic URL format as fallback
return f"https://{self.bucket_name}.s3.{self.region}.amazonaws.com/{file_key}" return f"https://{self.bucket_name}.s3.{self.region}.amazonaws.com/{file_key}"
async def get_permanent_url(self, file_key: str) -> str: async def get_permanent_url(self, file_key: str) -> str:

View File

@@ -325,27 +325,30 @@ class FileStorageService:
) )
raise raise
async def get_file_url(self, file_key: str, expires: int = 3600) -> str: async def get_file_url(
self,
file_key: str,
expires: int = 3600,
file_name: Optional[str] = None,
) -> str:
""" """
Get an access URL for a file. Get an access URL for a file.
Args: Args:
file_key: The file key. file_key: The file key.
expires: URL validity period in seconds (default: 1 hour). expires: URL validity period in seconds (default: 1 hour).
file_name: If set, adds Content-Disposition: attachment to force download.
Returns: Returns:
URL for accessing the file. URL for accessing the file.
""" """
logger.debug(f"Getting file URL: file_key={file_key}, expires={expires}s") logger.debug(f"Getting file URL: file_key={file_key}, expires={expires}s")
try: try:
url = await self.storage.get_url(file_key, expires) url = await self.storage.get_url(file_key, expires, file_name=file_name)
logger.debug(f"File URL generated: file_key={file_key}") logger.debug(f"File URL generated: file_key={file_key}")
return url return url
except Exception as e: except Exception as e:
logger.error( logger.error(f"Error getting file URL: file_key={file_key}, error={str(e)}")
f"Error getting file URL: file_key={file_key}, error={str(e)}"
)
raise raise