mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 18:08:07 +08:00
feat(bundle): manifest-driven import with sandbox upload
- Add BundleManifest with dsl_filename for 100% tree ID restoration - Implement two-step import flow: prepare (get upload URL) + confirm - Use sandbox for zip extraction and file upload via presigned URLs - Store import session in Redis with 1h TTL - Add SandboxUploadItem for symmetric download/upload API - Remove legacy source_zip_extractor, inline logic in service - Update frontend to use new prepare/confirm API flow
This commit is contained in:
@ -152,6 +152,20 @@ class _BundleExportZipAssetPath(SignedAssetPath):
|
||||
return [self.asset_type, self.tenant_id, self.app_id, self.resource_id]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BundleImportZipPath:
|
||||
"""Path for temporary import zip files. Not signed, uses direct presign URLs only."""
|
||||
|
||||
tenant_id: str
|
||||
import_id: str
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
_require_uuid(self.tenant_id, "tenant_id")
|
||||
|
||||
def get_storage_key(self) -> str:
|
||||
return f"{_ASSET_BASE}/{self.tenant_id}/imports/{self.import_id}.zip"
|
||||
|
||||
|
||||
class AssetPath:
|
||||
@staticmethod
|
||||
def draft(tenant_id: str, app_id: str, node_id: str) -> SignedAssetPath:
|
||||
@ -177,6 +191,10 @@ class AssetPath:
|
||||
def bundle_export_zip(tenant_id: str, app_id: str, export_id: str) -> SignedAssetPath:
|
||||
return _BundleExportZipAssetPath(tenant_id=tenant_id, app_id=app_id, resource_id=export_id)
|
||||
|
||||
@staticmethod
|
||||
def bundle_import_zip(tenant_id: str, import_id: str) -> BundleImportZipPath:
|
||||
return BundleImportZipPath(tenant_id=tenant_id, import_id=import_id)
|
||||
|
||||
@staticmethod
|
||||
def from_components(
|
||||
asset_type: str,
|
||||
@ -386,6 +404,23 @@ class AppAssetStorage:
|
||||
|
||||
return self._generate_signed_proxy_upload_url(asset_path, expires_in)
|
||||
|
||||
def get_import_upload_url(self, path: BundleImportZipPath, expires_in: int = 3600) -> str:
|
||||
"""Get upload URL for import zip (direct presign, no proxy fallback)."""
|
||||
return self._storage.get_upload_url(path.get_storage_key(), expires_in)
|
||||
|
||||
def get_import_download_url(self, path: BundleImportZipPath, expires_in: int = 3600) -> str:
|
||||
"""Get download URL for import zip (direct presign, no proxy fallback)."""
|
||||
return self._storage.get_download_url(path.get_storage_key(), expires_in)
|
||||
|
||||
def delete_import_zip(self, path: BundleImportZipPath) -> None:
|
||||
"""Delete import zip file. Errors are logged but not raised."""
|
||||
try:
|
||||
self._storage.delete(path.get_storage_key())
|
||||
except Exception:
|
||||
import logging
|
||||
|
||||
logging.getLogger(__name__).debug("Failed to delete import zip: %s", path.get_storage_key())
|
||||
|
||||
def _generate_signed_proxy_download_url(self, asset_path: SignedAssetPath, expires_in: int) -> str:
|
||||
expires_in = min(expires_in, dify_config.FILES_ACCESS_TIMEOUT)
|
||||
expires_at = int(time.time()) + max(expires_in, 1)
|
||||
|
||||
Reference in New Issue
Block a user